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程序中的常见漏洞进行攻击(下)
Oct 09 PHP
无法在发生错误时创建会话,请检查 PHP 或网站服务器日志,并正确配置 PHP 安装最快的解决办法
Aug 01 PHP
PHP中foreach循环中使用引用要注意的地方
Jan 02 PHP
PHP正则提取不包含指定网址的图片地址的例子
Apr 21 PHP
php中chdir()函数用法实例
Nov 13 PHP
php实现可逆加密的方法
Aug 11 PHP
分享PHP守护进程类
Dec 30 PHP
人脸识别测颜值、测脸龄、测相似度微信接口
Apr 07 PHP
PHP实现的文件上传类与用法详解
Jul 05 PHP
PHP实现的无限分类类库定义与用法示例【基于thinkPHP】
Aug 06 PHP
php ZipArchive实现多文件打包下载实例
Oct 31 PHP
PHP使用递归按层级查找数据的方法
Nov 10 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+JS无限级可伸缩菜单详解(简单易懂)
2007/01/02 PHP
PHP 网页过期时间的控制代码
2009/06/29 PHP
php class中self,parent,this的区别以及实例介绍
2013/04/24 PHP
PHP XML Expat解析器知识点总结
2019/02/15 PHP
浅谈laravel中的关联查询with的问题
2019/10/10 PHP
jQuery之end()和pushStack()使用介绍
2012/02/07 Javascript
javascript基本算法汇总
2016/03/09 Javascript
Javascript实现苹果悬浮虚拟按钮
2016/04/10 Javascript
JS模仿腾讯图片站的图片翻页按钮效果完整实例
2016/06/21 Javascript
js判断checkbox是否选中个数的方法(超简单)
2016/08/19 Javascript
js实现表单及时验证功能 用户信息立即验证
2016/09/13 Javascript
jquery实现图片轮播器
2017/05/23 jQuery
js中位运算的运用实例分析
2018/12/11 Javascript
JS apply用法总结和使用场景实例分析
2020/03/14 Javascript
js绘制一条直线并旋转45度
2020/08/21 Javascript
angular共享依赖的解决方案分享
2020/10/15 Javascript
es5 类与es6中class的区别小结
2020/11/09 Javascript
[05:04]DOTA2上海特级锦标赛主赛事第二日TOP10
2016/03/04 DOTA
Python中遇到的小问题及解决方法汇总
2017/01/11 Python
Python实现压缩文件夹与解压缩zip文件的方法
2018/09/01 Python
python numpy数组的索引和切片的操作方法
2018/10/20 Python
python命令行参数用法实例分析
2019/06/25 Python
python tkinter组件使用详解
2019/09/16 Python
django 数据库返回queryset实现封装为字典
2020/05/19 Python
Python中如何引入第三方模块
2020/05/27 Python
荷兰手表网站:Watch2Day
2018/07/02 全球购物
La Senza官网:北美顶尖性感内衣品牌
2018/08/03 全球购物
茱莉蔻美国官网:Jurlique美国
2020/11/24 全球购物
服装公司总经理岗位职责
2013/11/30 职场文书
药店促销活动总结
2014/07/10 职场文书
语文复习计划
2015/01/19 职场文书
高中运动会前导词
2015/07/20 职场文书
幼儿园教师辞职信
2019/06/21 职场文书
mysql如何能有效防止删库跑路
2021/10/05 MySQL
MyBatis 动态SQL全面详解
2021/10/05 MySQL
Python数据可视化之Seaborn的安装及使用
2022/04/19 Python