PHP 引用是个坏习惯


Posted in PHP onMarch 12, 2010
function binsearch(&$arr, $key, $value) 
{ 
$low = 0; 
$high = count($arr); 
while ($low <= $high) { 
$mid = floor($low + ($high - $low) / 2); 
$item = $arr[$mid][$key]; 
if ($item == $value) { 
return $mid; 
} else if ($value > $item) { 
$low = $mid + 1; 
} else { 
$high = $mid - 1; 
} 
} 
return false; 
}

在这里,$mid 采用了先减后加的方法计算,目的是为了防止整数的溢出。不是故意写复杂了。
我用下面的代码进行测试:
$data = array(); 
for ($i = 0; $i < 1000000; $i++) 
{ 
$data[] = array("sq" => $i * 2); 
} 
var_dump(binsearch($data, "sq", 10000));

发现,binsearch 的时候,总是要花个 0.2s左右。理论上来说,100万的数据,最多也就是循环20次。怎么会这样慢呢。
后来监控了一下内存,data 数组 占用了 230M 的内存。而 binsearch 的时候,占用了60K 的内存。但是,理论上来说,binsearch
不应该占用如此多的内存。因为,我觉得,我已经用引用了,根本就没有对data 的结构进行修改。
我也是百思不得其解,后来,我把引用参数去掉,居然 binsearch 只要 0.0002s ,看来是引用耗费了大量的cpu 资源。
PHP 内部遵循一个copy on write 的原则。实际上这个引用是多余的。
但是为什么,加了引用速度会变慢呢?今天重点就谈谈这个问题。明白道理后,大家一定知道怎么用引用了。
如果在binsearch 调用前,直接 $a = &$data,这个引用的速度会非常的快。看来肯定不是引用本身产生的问题。
这个问题,实际上涉及了zend 引擎如何管理PHP变量。
先看下面的问题:
<?php 
function demo(&$a, &$b) { $a =& $b; } 
$a = 1; 
$b = 2; 
demo($a, $b); 
$b = 3; 
print $a; 
?>

$a 输出是多少呢?不错,是2. 不过,我一开始觉得是3。
那么怎么解释上面这个问题呢?
实际上,函数的参数引用是这样进行的。
$tmp = $a; 
$a1 = &$tmp; 
$a = $tmp; 
unset($a1, $tmp);

这里,引用的实际上是一个临时变量。这个时候,$tmp 是带引用属性的,而$a 变量不是带引用属性的。
根据zend引擎管理内存的方法,在内部,不能用一个zval 来表示,必须强制分离这个zval。
用这样的理解方法,上面的问题就解决了。函数内部,不会改变函数外部的引用特性。这也是PHP
不赞成用 calltime_by_ref 的原因,而选择上面如此低效的拷贝方法。
下面的分析,也能证明,在传递参数时,的确发生了拷贝。
在 binsearch 函数里面。
$data[0] = 1;
这样,就会发生一次$data 所在zval 的拷贝。内存使用量 就是 60K。和函数调用加引用一模一样。
可能很多人会疑问,为什么不是多了230M呢,这其实就是PHP的高明之处,数组Key 对应的是一个zval的指针。(内部是一个哈希表)
所以,只要把这些指针复制一遍就就好了,数据不用复制。但是,100万的PHP 哈希表实际上要占用 50M 内存。为什么只有60K呢。
在 binsearch 函数的外面,运行
$t = $data; 
$t[0] = 1; 
unset($t);

果然,多了60K 的内存。估计和PHP的内存管理机制有关系。
现在一切都明白了吧!今天,想了好几个小时,才把这个问题想通,不敢独享。
函数中的引用不是给你传参数方便的,而是让你实现,一个函数,可以有多个返回值的,所以,最好不要画蛇添足。
实际上,用引用它会降低性能。
PHP 相关文章推荐
用PHP控制用户的浏览器--ob*函数的使用说明
Mar 16 PHP
linux php mysql数据库备份实现代码
Mar 10 PHP
php中通过虚代理实现延迟加载的实现代码
Jun 10 PHP
php报表之jpgraph柱状图实例代码
Aug 22 PHP
php通过array_merge()函数合并两个数组的方法
Mar 18 PHP
PHP file_get_contents函数读取远程数据超时的解决方法
May 13 PHP
PHP微信红包API接口
Dec 05 PHP
Joomla开启SEF的方法
May 04 PHP
PHP实现QQ登录的开原理和实现过程
Feb 04 PHP
PHP实现的简单留言板功能示例【基于thinkPHP框架】
Dec 07 PHP
PHP微信支付结果通知与回调策略分析
Jan 10 PHP
php7连接MySQL实现简易查询程序的方法
Oct 13 PHP
PHP 页面编码声明方法详解(header或meta)
Mar 12 #PHP
用PHP获取Google AJAX Search API 数据的代码
Mar 12 #PHP
PHP开启gzip页面压缩实例代码
Mar 11 #PHP
php checkdate、getdate等日期时间函数操作详解
Mar 11 #PHP
PHP 5.3新特性命名空间规则解析及高级功能
Mar 11 #PHP
PHP Memcached + APC + 文件缓存封装实现代码
Mar 11 #PHP
了解Joomla 这款来自国外的php网站管理系统
Mar 11 #PHP
You might like
配置最新的PHP加MYSQL服务器
2006/10/09 PHP
php xml-rpc远程调用
2008/12/19 PHP
PHP下escape解码函数的实现方法
2010/08/08 PHP
PHP中空字符串介绍0、null、empty和false之间的关系
2012/09/25 PHP
PHP文件锁函数flock()详细介绍
2014/11/18 PHP
PHP处理二进制数据的实现方法
2016/06/13 PHP
PHP5.4起内置web服务器使用方法
2016/08/09 PHP
php提供实现反射的方法和实例代码
2019/09/17 PHP
php多进程中的阻塞与非阻塞操作实例分析
2020/03/04 PHP
学习YUI.Ext 第六天--关于树TreePanel(Part 2异步获取节点)
2007/03/10 Javascript
jQuery Ajax使用 全解析
2010/12/15 Javascript
分享10篇优秀的jQuery幻灯片制作教程及应用案例
2011/04/16 Javascript
jQuery圆形统计图开发实例
2015/01/04 Javascript
jquery仿QQ登录账号选择下拉框效果
2016/03/22 Javascript
JS提示:Uncaught SyntaxError:Unexpected token ) 错误的解决方法
2016/08/19 Javascript
基于touch.js手势库+zepto.js插件开发图片查看器(滑动、缩放、双击缩放)
2016/11/17 Javascript
JS验证不重复验证码
2017/02/10 Javascript
Three.js利用Detector.js插件如何实现兼容性检测详解
2017/09/26 Javascript
vue中axios处理http发送请求的示例(Post和get)
2017/10/13 Javascript
springMvc 前端用json的方式向后台传递对象数组方法
2018/08/07 Javascript
Vue 2.0双向绑定原理的实现方法
2019/10/23 Javascript
Vue实现手机计算器
2020/08/17 Javascript
k8s node节点重新加入master集群的实现
2021/02/22 Javascript
Python实现二分法算法实例
2015/02/02 Python
Python中IPYTHON入门实例
2015/05/11 Python
Python中List.count()方法的使用教程
2015/05/20 Python
Python的“二维”字典 (two-dimension dictionary)定义与实现方法
2016/04/27 Python
python 实现自动远程登陆scp文件实例代码
2017/03/13 Python
基于python中staticmethod和classmethod的区别(详解)
2017/10/24 Python
Mac中Python 3环境下安装scrapy的方法教程
2017/10/26 Python
Python列表推导式与生成器用法分析
2018/08/02 Python
Python 类的私有属性和私有方法实例分析
2019/09/29 Python
投标承诺书范本
2014/03/27 职场文书
政工例会汇报材料
2014/08/26 职场文书
民事纠纷协议书
2016/03/23 职场文书
世界无敌的ICOM IC-R9500宽频接收机
2022/03/25 无线电