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 相关文章推荐
Ajax+PHP 边学边练 之二 实例
Nov 24 PHP
php获取用户浏览器版本的方法
Jan 03 PHP
Symfony2函数用法实例分析
Mar 18 PHP
总结PHP如何获取当前主机、域名、网址、路径、端口和参数等
Sep 09 PHP
WAF的正确bypass
Jan 05 PHP
php+redis实现多台服务器内网存储session并读取示例
Jan 12 PHP
Yii2实现跨mysql数据库关联查询排序功能代码
Feb 10 PHP
微信封装的调用微信签名包的类库
Jun 08 PHP
PHP实现使用DOM将XML数据存入数组的方法示例
Sep 27 PHP
Mac系统下安装PHP Xdebug
Mar 30 PHP
PHP模版引擎原理、定义与用法实例
Mar 29 PHP
laravel-admin 管理平台获取当前登陆用户信息的例子
Oct 08 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 ajax实现文件上传进度条
2016/03/29 PHP
ThinkPHP中create()方法自动验证表单信息
2017/04/28 PHP
关于js类的定义
2011/06/28 Javascript
解析javascript 数组以及json元素的添加删除
2013/06/26 Javascript
仿百度联盟对联广告实现代码
2014/08/30 Javascript
js添加select下默认的option的value和text的方法
2014/10/19 Javascript
Javascript模拟加速运动与减速运动代码分享
2014/12/11 Javascript
JQuery中$.each 和$(selector).each()的区别详解
2015/03/13 Javascript
jQuery on()方法绑定动态元素的点击事件实例代码浅析
2016/06/16 Javascript
jQuery动态生成Bootstrap表格
2016/11/01 Javascript
微信小程序实战之仿android fragment可滑动底部导航栏(4)
2020/04/16 Javascript
async/await地狱该如何避免详解
2018/05/10 Javascript
JS基于封装函数实现的表格分页完整示例
2018/06/26 Javascript
解决微信小程序中转换时间格式IOS不兼容的问题
2019/02/15 Javascript
vue实现下拉加载其实没那么复杂
2019/08/13 Javascript
[01:03:31]DOTA2上海特级锦标赛B组资格赛#1 Alliance VS Fnatic第二局
2016/02/26 DOTA
[52:26]完美世界DOTA2联赛决赛 FTD vs Phoenix 第一场 11.08
2020/11/11 DOTA
python抽取指定url页面的title方法
2018/05/11 Python
对python实现合并两个排序链表的方法详解
2019/01/23 Python
python3.6中@property装饰器的使用方法示例
2019/08/17 Python
K最近邻算法(KNN)---sklearn+python实现方式
2020/02/24 Python
Python + selenium + crontab实现每日定时自动打卡功能
2020/03/31 Python
python中线程和进程有何区别
2020/06/17 Python
公司财务自我评价分享
2013/12/17 职场文书
生物学学生自我评价
2014/01/17 职场文书
《巨人的花园》教学反思
2014/02/12 职场文书
热情服务标语
2014/10/07 职场文书
个人对照检查剖析材料
2014/10/13 职场文书
质监局领导班子践行群众路线整改方案
2014/10/26 职场文书
2015年仓库管理工作总结
2015/05/25 职场文书
校园新闻稿范文
2015/07/18 职场文书
比较node.js和Deno
2021/04/27 Javascript
浅谈Laravel中使用Slack进行异常通知
2021/05/29 PHP
Python使用MapReduce进行简单的销售统计
2022/04/22 Python
MySQL的存储过程和相关函数
2022/04/26 MySQL
HTML静态页面获取url参数和UserAgent的实现
2022/08/05 HTML / CSS