当前位置:Gxlcms > PHP教程 > 遭遇php的in_array低性能问题_php技巧

遭遇php的in_array低性能问题_php技巧

时间:2021-07-01 10:21:17 帮助过:45人阅读

PHP的性能一直在提高。然而,若是用的不恰当,或是一个不留神,还是可能会踩到PHP内部实现方面的坑的。我在前几天的一个性能问题上就碰到了。

事情是这样子的,一位同事反馈我们的一个接口每次返回需要5秒之久,我们一起review了代码,“惊喜”的发现居然在循环(大约900次)中调用了一个读缓存的操作,而这个缓存的key并没有改变,因此我们把这段代码移到了循环外面,再测,接口返回时间降到了2秒,呜呼!虽然提升了1倍,但明显不是我们能接受的结果!
出现性能问题的代码量并不大,我们排除了IO问题以后,写了一段测试代码,果然问题很快重现。

代码如下:
  1. <br><!--?php <BR-->$y="1800"; <br>$x = array(); <br>for($j=0;$j<2000;$j++){ <br>$x[]= "{$j}"; <br>} <br><br>for($i=0;$i<3000;$i++){ <br>if(in_array($y,$x)){ <br>continue; <br>} <br>} <br>?> <br> <br><br>shell$ time /usr/local/php/bin/php test.php <br><br>real 0m1.132s <br>user 0m1.118s <br>sys 0m0.015s <br><br>对的,我们用的就是字符串型的数字,从缓存拿出来就是这样子的啦!所以这里是特意转成字符串的(如果直接是数字,并不会出现这个问题 ,各位可以自行验证)。可以看出时间耗掉了1秒,才3000次循环,后面的sys用时也注定我们用strace不会拿到什么有效信息。 <br><br>shell$ strace -ttt -o xxx /usr/local/php/bin/php test.php <br>shell$ less xxx <br><br><img class="attachment-medium" alt="in_array.strace" src="https://img.gxlcms.com//Uploads-s/new/2019-09-20-201920/2013091715532520.png" width="774" height="162"><br><br>我们只看到这两次系统调用之间的延时非常大,却并不知道干了什么?一筹莫展了,幸好,Linux下的调试利器除了strace还有ltrace(当然还有dtrace,ptrace,不在本文讨论范围了,略去)。 <br><br>引用:strace用来 跟踪一个进程的系统调用或信号产生的情况,而 ltrace用来 跟踪进程调用库函数的情况(via IBM developerworks)。 <br><br>为了排除干扰因素,我们将$x直接赋值为array(“0″,”1″,”2″,……)的形式,避免过多的malloc调用影响结果。执行 <br><br>shell$ ltrace -c /usr/local/php/bin/php test.php <br><br>如图2<br><br><img class="attachment-medium" alt="in_array.ltrace1" src="https://img.gxlcms.com//Uploads-s/new/2019-09-20-201920/2013091715532521.png" width="538" height="344"><br><br><br>我们看到库函数__strtol_internal的调用非常之频繁,达到了94%,太夸张了,然后我又查了一下这个库函数__strtol_internal是干嘛的,原来是strtol的别名,简单的说就是把字符串转换成长整形,可以猜测PHP引擎已经检测到这是一个字符串型的数字,所以期望将他们转换成长整型来比较,这个转换过程中消耗了太多时间,我们再次执行:<br><br><span><u></u></span> 代码如下:<pre class="brush:php;toolbar:false layui-box layui-code-view layui-code-notepad"><ol class="layui-code-ol"><li><br>shell$ ltrace -e "__strtol_internal" /usr/local/php/bin/php test.php <br> <br><br>可以轻松抓到大量下图这样的调用,到此,问题找到了,in_array这种松比较,会将两个字符型数字串先转换为长整型再进行比较,却不知性能就耗在这上面了。<br><br><img class="attachment-medium" alt="tu3" src="https://img.gxlcms.com//Uploads-s/new/2019-09-20-201920/2013091715532522.png" width="623" height="358"><br><br>知道了症结所在,我们解决的办法就很多了,最简单的就是为in_array加第三个参数为true,即变为严格比较,同时还要比较类型,这样避免了PHP自作聪明的转换类型,跑起来果然快多了,代码如下:<p></p></li><li><p><span><u></u></span> 代码如下:<br><!--?php<BR-->$y="1800";<br>$x = array();<br>for($j=0;$j<2000;$j++){<br> $x[]= "{$j}";<br>}</p></li><li><p>for($i=0;$i<3000;$i++){<br> if(in_array($y,$x,true)){<br> continue;<br> }<br>}<br>?> <br></p></li><li><p><span><u></u></span> 代码如下:</p><pre class="brush:php;toolbar:false layui-box layui-code-view layui-code-notepad"><ol class="layui-code-ol"><li><br>shell$ time /usr/local/php/bin/php test.php <br><br>real 0m0.267s <br>user 0m0.247s <br>sys 0m0.020s <br> <br><br>快了好多倍啊!!!可以看到sys耗时几乎没有太大变化。我们再次ltrace一把,还是要把$x直接赋值,排除malloc调用的干扰,因为我们实际应用中是从缓存里一次拉出来的,所以也不存在示例代码中这样的循环来申请内存的情况。 <br>再次执行 <br><br><span><u></u></span> 代码如下:<pre class="brush:php;toolbar:false layui-box layui-code-view layui-code-notepad"><ol class="layui-code-ol"><li><br>shell$ ltrace -c /usr/local/php/bin/php test.php <br> <br><br>如下图:<p></p></li><li><p><img class="attachment-medium" alt="in_array.ltrace2" src="https://img.gxlcms.com//Uploads-s/new/2019-09-20-201920/2013091715532523.png" width="528" height="338"></p></li><li><p>__ctype_tolower_loc占用了最多的时间!查了一下库函数__ctype_tolower_loc是干嘛的:简单的理解是将字符串转换成小写,那么这说明in_array比较字符串不区分大小写吗?其实这个函数调用已经和我们这个in_array感觉联系不大了,关于in_array的实现,还是去看看PHP的源码,大概理解的更为透彻了,好了,没法往下说了,欢迎与我交流,写的不对的地方请多多斧正。<br><br>———————2013.08.29分割线——————————<br><br>晚上又翻了以下PHP 5.4.10的源码,对in_array的兴趣真大啊,哈哈,位于./ext/standard/array.c的第1248行,可以看到他调用了php_search_array函数,下面的array_serach也是调的这个,只是最后一个参数不同!经过一番跟踪,在in_array松比较的情况下,他最终调用的函数 zendi_smart_strcmp(果然是个“聪明”函数)进行比较,位于./Zend/zend_operators.c,我们用ltrace抓到的大量转换成整型的操作就是那个is_numeric_string_ex的行为。<br><br><img class="attachment-medium" alt="%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7-2013-08-29-23.13.20" src="https://img.gxlcms.com//Uploads-s/new/2019-09-20-201920/2013091715532524.png" width="1638" height="1340"><br><br>函数is_numeric_string_ex是在./Zend/zend_operators.h中定义的,在前面进行了一堆的判断和转换之后,在232行调用了strtol,就是我们在文章中提到的系统函数了,将字符串转换成长整型,有图有真相<br><br><img class="attachment-medium" alt="zend_operators-is_num" src="https://img.gxlcms.com//Uploads-s/new/2019-09-20-201920/2013091715532525.png" width="792" height="168"> </p></li></ol></pre></li></ol></pre></li></ol></pre>

人气教程排行