PHP interpreter micro-optimizations in code
通过在这个so线程上绊倒,我决定用PHP编写类似的测试。我的测试代码是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // Slow version $t1 = microtime(true); for ($n = 0, $i = 0; $i < 20000000; $i++) { $n += 2 * ($i * $i); } $t2 = microtime(true); echo"n={$n} "; // Optimized version $t3 = microtime(true); for ($n = 0, $i = 0; $i < 20000000; $i++) { $n += $i * $i; } $n *= 2; $t4 = microtime(true); echo"n={$n} "; $speedup = round(100 * (($t2 - $t1) - ($t4 - $t3)) / ($t2 - $t1), 0); echo"speedup: {$speedup}% "; |
结果
优化的基本原理
我将不再详细讨论,但优化和未优化代码中的乘法比率是->
1求和:3/42求和:4/63求和:5/84求和:6/10…
一般情况下:
其中n是一个循环中的求和数。为了对我们有用,我们需要计算当n接近无穷大时它的极限(为了复制我们在一个循环中做很多求和的情况)。所以:
所以我们得出结论,在优化的代码中,必须减少50%的乘法。
问题是时候分析由PHP解释器生成的PHP操作码了。为此,您需要安装VLD扩展并从命令行使用它来生成手头的PHP脚本的操作码。
Opcode分析1 2 | POST_INC ~4 !1 FREE ~4 |
将计数器增加1,并将以前的值保存到内存插槽4中。然后,因为从未使用过这个值-将其从内存中释放。问题-如果不使用价值,为什么我们需要存储价值?
优化测试代码
将post_inc更改为assign_add(它不会在内存中保存附加信息)并执行循环展开,将使用以下测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | while (true) { // Slow version $t1 = microtime(true); for ($n = 0, $i = 0; $i < 2000; $i+=10) { // loop unrolling $n += 2 * (($i+0) * ($i+0)); $n += 2 * (($i+1) * ($i+1)); $n += 2 * (($i+2) * ($i+2)); $n += 2 * (($i+3) * ($i+3)); $n += 2 * (($i+4) * ($i+4)); $n += 2 * (($i+5) * ($i+5)); $n += 2 * (($i+6) * ($i+6)); $n += 2 * (($i+7) * ($i+7)); $n += 2 * (($i+8) * ($i+8)); $n += 2 * (($i+9) * ($i+9)); } $t2 = microtime(true); echo"{$n} "; // Optimized version $t3 = microtime(true); for ($n = 0, $i = 0; $i < 2000; $i+=10) { // loop unrolling $n += ($i+0) * ($i+0); $n += ($i+1) * ($i+1); $n += ($i+2) * ($i+2); $n += ($i+3) * ($i+3); $n += ($i+4) * ($i+4); $n += ($i+5) * ($i+5); $n += ($i+6) * ($i+6); $n += ($i+7) * ($i+7); $n += ($i+8) * ($i+8); $n += ($i+9) * ($i+9); } $n *= 2; $t4 = microtime(true); echo"{$n} "; $speedup = round(100 * (($t2 - $t1) - ($t4 - $t3)) / ($t2 - $t1), 0); $table[$speedup]++; echo"**************** "; foreach ($table as $s => $c) { if ($s >= 0 && $s <= 20) echo"$s,$c "; } } |
结果
脚本聚合CPU命中到一个或其他加速值的次数。当CPU点击加速比被绘制为图表时,我们得到这样的图片:
所以很可能脚本会加速10%。这意味着我们的优化导致了+2%的加速(与原始脚本8%相比)。
期望我非常确定我所做的所有这些事情——都可以通过一个php jit'er自动完成。我认为在生成二进制可执行文件时,很难将一对post_inc/free操作码自动更改为一个pre_inc操作码。此外,php jit'er可以应用循环展开也不是一个奇迹。这只是一个优化的开始!
希望在php 8.0中有一个jit'er