遭遇php的in_array低性能问题


Posted in PHP onSeptember 17, 2013

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

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

<?php 
$y="1800"; 
$x = array(); 
for($j=0;$j<2000;$j++){ 
$x[]= "{$j}"; 
} for($i=0;$i<3000;$i++){ 
if(in_array($y,$x)){ 
continue; 
} 
} 
?>

shell$ time /usr/local/php/bin/php test.php

real 0m1.132s
user 0m1.118s
sys 0m0.015s

对的,我们用的就是字符串型的数字,从缓存拿出来就是这样子的啦!所以这里是特意转成字符串的(如果直接是数字,并不会出现这个问题 ,各位可以自行验证)。可以看出时间耗掉了1秒,才3000次循环,后面的sys用时也注定我们用strace不会拿到什么有效信息。

shell$ strace -ttt -o xxx /usr/local/php/bin/php test.php
shell$ less xxx

遭遇php的in_array低性能问题

我们只看到这两次系统调用之间的延时非常大,却并不知道干了什么?一筹莫展了,幸好,Linux下的调试利器除了strace还有ltrace(当然还有dtrace,ptrace,不在本文讨论范围了,略去)。

引用:strace用来 跟踪一个进程的系统调用或信号产生的情况,而 ltrace用来 跟踪进程调用库函数的情况(via IBM developerworks)。

为了排除干扰因素,我们将$x直接赋值为array(“0″,”1″,”2″,……)的形式,避免过多的malloc调用影响结果。执行

shell$ ltrace -c /usr/local/php/bin/php test.php

如图2

遭遇php的in_array低性能问题

我们看到库函数__strtol_internal的调用非常之频繁,达到了94%,太夸张了,然后我又查了一下这个库函数__strtol_internal是干嘛的,原来是strtol的别名,简单的说就是把字符串转换成长整形,可以猜测PHP引擎已经检测到这是一个字符串型的数字,所以期望将他们转换成长整型来比较,这个转换过程中消耗了太多时间,我们再次执行:

shell$ ltrace -e "__strtol_internal" /usr/local/php/bin/php test.php

可以轻松抓到大量下图这样的调用,到此,问题找到了,in_array这种松比较,会将两个字符型数字串先转换为长整型再进行比较,却不知性能就耗在这上面了。

遭遇php的in_array低性能问题

知道了症结所在,我们解决的办法就很多了,最简单的就是为in_array加第三个参数为true,即变为严格比较,同时还要比较类型,这样避免了PHP自作聪明的转换类型,跑起来果然快多了,代码如下:

<?php
$y="1800";
$x = array();
for($j=0;$j<2000;$j++){
        $x[]= "{$j}";
}
for($i=0;$i<3000;$i++){
        if(in_array($y,$x,true)){
                continue;
        }
}
?>
shell$ time /usr/local/php/bin/php test.php real 0m0.267s 
user 0m0.247s 
sys 0m0.020s

快了好多倍啊!!!可以看到sys耗时几乎没有太大变化。我们再次ltrace一把,还是要把$x直接赋值,排除malloc调用的干扰,因为我们实际应用中是从缓存里一次拉出来的,所以也不存在示例代码中这样的循环来申请内存的情况。
再次执行

shell$ ltrace -c /usr/local/php/bin/php test.php

如下图:

遭遇php的in_array低性能问题

__ctype_tolower_loc占用了最多的时间!查了一下库函数__ctype_tolower_loc是干嘛的:简单的理解是将字符串转换成小写,那么这说明in_array比较字符串不区分大小写吗?其实这个函数调用已经和我们这个in_array感觉联系不大了,关于in_array的实现,还是去看看PHP的源码,大概理解的更为透彻了,好了,没法往下说了,欢迎与我交流,写的不对的地方请多多斧正。

———————2013.08.29分割线——————————

晚上又翻了以下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的行为。

遭遇php的in_array低性能问题

函数is_numeric_string_ex是在./Zend/zend_operators.h中定义的,在前面进行了一堆的判断和转换之后,在232行调用了strtol,就是我们在文章中提到的系统函数了,将字符串转换成长整型,有图有真相

遭遇php的in_array低性能问题

PHP 相关文章推荐
PHP 5.0对象模型深度探索之绑定
Sep 05 PHP
php session安全问题分析
Jun 24 PHP
基于php iconv函数的使用详解
Jun 09 PHP
自己写了一个php检测文件编码的函数
Apr 21 PHP
php中mysql操作buffer用法详解
Mar 19 PHP
PHP编程中的__clone()方法使用详解
Nov 27 PHP
php基于mcrypt_encrypt和mcrypt_decrypt实现字符串加密解密的方法
Jul 12 PHP
php利用header函数下载各种文件
Aug 24 PHP
PHP正则匹配反斜杠'\'和美元'$'的方法
Feb 08 PHP
php写app接口并返回json数据的实例(分享)
May 20 PHP
PHP实现负载均衡下的session共用功能
Apr 17 PHP
php设计模式之状态模式实例分析【星际争霸游戏案例】
Mar 26 PHP
PHP和JavaScrip分别获取关联数组的键值示例代码
Sep 16 #PHP
PHP中的str_repeat函数在JavaScript中的实现
Sep 16 #PHP
改写函数实现PHP二维/三维数组转字符串
Sep 13 #PHP
php jq jquery getJSON跨域提交数据完整版
Sep 13 #PHP
PHP Session 变量的使用方法详解与实例代码
Sep 11 #PHP
php中通过数组进行高效随机抽取指定条记录的算法
Sep 09 #PHP
php文件夹与文件目录操作函数介绍
Sep 09 #PHP
You might like
joomla内置的表单验证功能使用方法
2010/06/11 PHP
Discuz Uchome ajaxpost小技巧
2011/01/04 PHP
apache mysql php 源码编译使用方法
2012/05/03 PHP
PHP的error_reporting错误级别变量对照表
2014/07/08 PHP
浅谈PHP中的错误处理和异常处理
2017/02/04 PHP
Yii redis集合的基本使用教程
2020/06/14 PHP
javascript 导出数据到Excel(处理table中的元素)
2009/12/18 Javascript
基于jquery实现后台左侧菜单点击上下滑动显示
2013/04/11 Javascript
使用jquery实现放大镜效果
2014/09/02 Javascript
javascript基础语法——全面理解变量和标识符
2016/06/02 Javascript
浅谈js内置对象Math的属性和方法(推荐)
2016/09/19 Javascript
jQuery和JavaScript节点插入元素的方法对比
2016/11/18 Javascript
Angular.js中ng-include用法及多标签页面的实现方式详解
2017/05/07 Javascript
jQuery实现的鼠标滚轮控制图片缩放功能实例
2017/10/14 jQuery
vue底部加载更多的实例代码
2018/06/29 Javascript
详解jQuery获取特殊属性的值以及设置内容
2018/11/14 jQuery
Node.js之readline模块的使用详解
2019/03/25 Javascript
Postman动态获取返回值过程详解
2020/06/30 Javascript
[01:04:05]VG vs Newbee 2018国际邀请赛小组赛BO2 第一场 8.17
2018/08/20 DOTA
Python对数据库操作
2016/03/28 Python
python xlsxwriter库生成图表的应用示例
2018/03/16 Python
python PyAutoGUI 模拟鼠标键盘操作和截屏功能
2019/08/04 Python
python实现一个函数版的名片管理系统过程解析
2019/08/27 Python
Python程序慢的重要原因
2020/09/04 Python
pycharm 使用anaconda为默认环境的操作
2021/02/05 Python
CSS3系列教程:背景图片(背景大小和多背景图) 应用说明
2012/12/19 HTML / CSS
购买一个高级域名:BuyDomains
2018/03/11 全球购物
美赞臣营养马来西亚旗舰店:Enfagrow马来西亚
2019/07/26 全球购物
美国翻新电子产品商店:The Store
2019/10/08 全球购物
设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1。写出程序。
2014/12/30 面试题
行政助理岗位职责
2013/11/10 职场文书
公司拓展活动方案
2014/02/13 职场文书
求职信名称怎么写
2014/05/26 职场文书
校庆口号
2014/06/20 职场文书
2016年秋季运动会加油稿
2015/12/21 职场文书
python tkinter实现定时关机
2021/04/21 Python