译者 | 刘汪洋
审校 | 重楼
如果在阅读这篇文章之前 , 你还不了解“十亿行挑战”( The One Billion Row Challenge,1brc ),我推荐你访问 Gunnar Morling 的 1brc Github 代码仓库了解更多详情 。
我有两位同事已经参与这项挑战并成功上榜,因此我也选择加入 。
虽然 php 的执行速度并不出名,但我正开发一个 PHP 分析器 , 因此我想亲自测试一下 PHP的处理速度 。
第一种尝试:简单直接的方法我首先克隆了挑战的代码仓库,并生成了一个包含十亿行数据的文件measurements.txt 。接下来,我开始尝试第一个解决方案:
<?php$stations = [];$fp = fopen('measurements.txt', 'r');while ($data = https://www.isolves.com/it/cxkf/yy/php/2024-04-23/fgetcsv($fp, null, ';')) {if (!isset($stations[$data[0]])) {$stations[$data[0]] = [$data[1],$data[1],$data[1],1];} else {$stations[$data[0]][3]++;$stations[$data[0]][2] += $data[1];if ($data[1] < $stations[$data[0]][0]) {$stations[$data[0]][0] = $data[1];}if ($data[1] > $stations[$data[0]][1]) {$stations[$data[0]][1] = $data[1];}}}ksort($stations);echo '{';foreach ($stations as $k => &$station) {$station[2] = $station[2] / $station[3];echo $k, '=', $station[0], '/', $station[2], '/', $station[1], ', ';}echo '}';
这段代码逻辑简单明了:打开文件并通过 fgetcsv()读取数据 。若之前未记录过该站点,则创建一个新条目;否则,进行计数器增加、温度累加,并检查当前温度是否刷新了最低或最高记录,如是 , 则进行更新 。
处理完所有数据后,我使用ksort()对数组$stations进行排序,并输出每个站点的最低温度、平均温度(总温度/记录数)和最高温度 。
令我惊讶的是,在我的笔记本电脑上运行这段简单脚本竟然耗时达到了25分钟 。
很明显,我需要对这段代码进行优化,并对其进行性能分析:
文章插图
通过可视化的时间线,我们可以分析出脚本运行明显受到 CPU 限制 , 脚本开始时的文件编译时间可以忽略不计,且几乎没有垃圾收集事件发生 。
文章插图
火焰图清晰地显示出,fgetcsv()函数占据了约 46% 的 CPU 时间 。
使用 fgets() 替代 fgetcsv()为了提升性能,我决定用fgets()替换fgetcsv()函数来逐行读取数据,并手动按;字符进行分割 。
// ...while ($data = https://www.isolves.com/it/cxkf/yy/php/2024-04-23/fgets($fp, 999)) {$pos = strpos($data, ';');$city = substr($data, 0, $pos);$temp = substr($data, $pos + 1, -1);// ...
同时 , 我还把代码中的$data[0]重命名为$city , $data[1]重命名为$temp,以增强代码的可读性 。这个简单的修改使得脚本运行时间大幅减少到 19 分钟 49 秒,虽然时间仍然较长 , 但相比之前已经减少了 21% 。
【使用 PHP 处理十亿行数据,如何极致提升处理速度?】
文章插图
通过火焰图的比较,可以看到在替换后 CPU 的时间利用率发生了变化,详细的根帧分析也揭示了具体的性能瓶颈位置:
文章插图
在脚本的第 18 行和第 23 行花费了大约 38% 的CPU时间 。
18 | $stations[$city][3]++;| // ...23 | if ($temp > $stations[$city][1]) {
第 18 行是数组$stations的首次访问和增量操作,而第 23 行进行了一次看似不那么耗时的比较操作 。尽管如此,进一步优化有助于揭示这些操作中潜在的性能开销 。尽可能使用引用为了提高性能,我决定在处理数组时使用引用,以避免每次访问数组时都对$stations数组中的键进行搜索 。这相当于为数组中的"当前"站点设置了一个缓存 。
代码如下:
$station = &$stations[$city];$station[3]++;$station[2] += $temp;// 替代原有的$stations[$city][3]++;$stations[$city][2] += $temp;
这一改变实际上大大减少了执行时间 , 将其缩短到 17 分钟 48 秒,进一步减少了 **10% **的运行时间 。条件判断优化在审查代码的过程中,我注意到了以下片段:
if ($temp < $station[0]) {$station[0] = $temp;} elseif ($temp > $station[1]) {$station[1] = $temp;}
考虑到一个温度值如果低于最小值,则不可能同时高于最大值 , 因此我使用elseif来优化条件判断,这可能会节省一些 CPU 周期 。需要指出的是,由于我不知道measurements.txt中温度值的排列顺序 , 根据这个顺序,首先检查最小值还是最大值可能会有所不同 。
推荐阅读
- MongoDB索引使用总结
- 皮衣染色了用什么可以消除 白色皮衣染色了处理的最好办法
- 冬笋如何处理剥壳 冬笋如何处理剥
- 敌百虫的作用,鱼缸敌百杀虫剂使用方法
- 制作火漆印章有危害么 火漆印章使用方法
- 吸油纸怎么正确使用 油烟机吸油纸怎么正确使用
- 洗手间臭味大怎么处理 洗手间臭味大的处理方法
- 离谱!尼古拉斯.凯奇非婚儿子殴打亲妈,还曾因家暴被处理
- 过期未拆封的食用油可以吃吗 过期未开封食用油怎么处理
- Excel要咋得才可以用宏,excel表格批次管理怎么使用宏