一、for循环对象长度两种写法
最近看到很多代码,各种语言,在写for循环的时候并没有把长度提取出来,而是每次遍历的时候进行计算。
$str = 'bye 2019, hi 2020!'; for ($i = 0; $i < strlen($str); $i++) { // do something }
这种写法在初入行的时候,总是被教育性能低,要把长度提取出来。这个在诸多代码优化中总是排名榜首。
$str = 'bye 2019, hi 2020!'; $len = strlen($str); for ($i = 0; $i < $len; $i++) { // do something }
之前在大脑中空想,认为确实应该提取出来,省的每次遍历调用函数。最近正好有空,测试了以下,跟想象的差距很大。两种写法在数据量级小(小于10万)的时候,差异并不大。
二、性能测试
PHP版本
php -v
PHP 7.3.9 (cli) (built: Sep 14 2019 18:12:27) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.9, Copyright (c) 1998-2018 Zend Technologies
with Zend OPcache v7.3.9, Copyright (c) 1999-2018, by Zend Technologies
1、测试脚本
for_performance_v1.php
$stime = microtime(true); $smem = memory_get_usage(); $multiplier = intval($argv[1]); $s = str_repeat('bye2019,hi2020!', $multiplier); for ($i = 0; $i < strlen($s); $i++) { echo $s[$i], PHP_EOL; } $etime = microtime(true); $emem = memory_get_usage(); echo 'exec time : ', round(($etime - $stime), 3), PHP_EOL; echo 'used memory(KB) : ', round(($emem - $smem) / 1024, 3), PHP_EOL;
for_performance_v2.php
$stime = microtime(true); $smem = memory_get_usage(); $multiplier = intval($argv[1]); $s = str_repeat('bye2019,hi2020!', $multiplier); $len = strlen($s); for ($i = 0; $i < $len; $i++) { echo $s[$i], PHP_EOL; } $etime = microtime(true); $emem = memory_get_usage(); echo 'exec time : ', round(($etime - $stime), 3), PHP_EOL; echo 'used memory(KB) : ', round(($emem - $smem) / 1024, 3), PHP_EOL;
2、脚本执行
php for_performance_v1.php 100000 php for_performance_v2.php 100000
3、性能结果
内存占用都一样,这里就不分开列出了。
+———–+——-+————+——+——+
| 长度 | v1 | v2 |内存 |
+———–+——-+————+——-+—–+
| 10000 | 0.429 | 0.477 | 148.031KB |
| 100000 | 3.923 | 3.283 | 1468.031KB |
| 100000 | 56.397| 40.27 | 14652.055KB |
+———–+——-+————+————-+
4、结果分析
这可能跟PHP运行时缓存有关。在执行期间,PHP经常需要根据名称去不同的哈希表中查找常量、函数、类、成员方法、成员属性等,因此PHP提供了一种缓存机制用于缓存根据名称查找到的结果,以便再次执行同一opcode时直接复用上次缓存的值,无需重复查找,从而提高执行效率。
三、总结思考
差距并没有我们想象中的大,6年的工作生产环境中,很少遇到超过10W的遍历。
多数人的错误,依旧是错误。代码的世界,实践比别人的结论更有意义。
参考:
PHP7内核剖析-运行时缓存