代码之家  ›  专栏  ›  技术社区  ›  Agnius Vasiliauskas

代码中的PHP解释器微优化

  •  1
  • Agnius Vasiliauskas  · 技术社区  · 5 年前

    在这上面绊倒 so thread 我决定用PHP编写类似的测试。 我的测试代码是:

    // Slow version
    $t1 = microtime(true);
    for ($n = 0, $i = 0; $i < 20000000; $i++) {
        $n += 2 * ($i * $i);
    }
    $t2 = microtime(true);
    echo "n={$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}\n";
    
    $speedup = round(100 * (($t2 - $t1) - ($t4 - $t3)) / ($t2 - $t1), 0);
    echo "speedup: {$speedup}%\n";
    

    结果

    1. 在PHP中 2 * ($i * $i) 版本运行非常类似 2 * $i * $i ,
      所以PHP解释器没有在Java中优化字节码作为JVM
    2. 即使我手动优化代码-我已经~ 8% 加速时 Java版本得到~ 16% 加速。所以PHP版本在Java代码中得到大约1/2的加速因子。

    优化的基本原理

    我将不再详细讨论,但优化和未优化代码中的乘法比率是->

    1求和:3/4
    2个求和:4/6
    3个总结:5/8
    4个总结:6/10

    一般情况下:

    enter image description here

    其中n是一个循环中的求和数。为了对我们有用,我们需要计算当n接近无穷大时它的极限(为了复制我们在循环中做很多求和的情况)。所以:

    enter image description here

    所以我们得出结论,在优化的代码中 50% 更少的乘法。

    问题

    1. 为什么PHP解释器不应用代码优化?
    2. 为什么PHP加速因子仅仅是Java中的一半?
    1 回复  |  直到 5 年前
        1
  •  0
  •   Agnius Vasiliauskas    5 年前

    是时候分析由PHP解释器生成的PHP操作码了。为此,您需要安装 VLD extension 在命令行中使用它来生成手头的PHP脚本的操作码。

    Opcode分析

    1. 似乎 $i++ 与…不同 ++$i 在操作码和内存使用方面。语句$I++;生成操作码:
     POST_INC ~4 !1
     FREE     ~4
    

    将计数器增加1,并将以前的值保存到内存插槽4中。然后,因为从未使用过这个值-将其从内存中释放。问题-如果不使用价值,为什么我们需要存储价值?

    1. 似乎确实存在循环惩罚,因此我们可以通过执行 循环展开 .

    优化测试代码

    将post_inc更改为assign_add(它不会在内存中保存附加信息)并执行循环展开,将使用以下测试代码:

    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}\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}\n";
    
    $speedup = round(100 * (($t2 - $t1) - ($t4 - $t3)) / ($t2 - $t1), 0);
    $table[$speedup]++;
    
    echo "****************\n";
    foreach ($table as $s => $c) {
      if ($s >= 0 && $s <= 20)
         echo "$s,$c\n";
    }
    
    }
    

    结果

    脚本将CPU命中一个或其他加速值的次数聚合起来。 当CPU命中vs加速被绘制成一个图表时,我们得到这样的图片:

    enter image description here

    所以很可能脚本会加速10%。这意味着我们的优化结果 + 2% 加速(与原始脚本8%相比)。

    期望

    我非常确定我所做的所有这些事情——都可以通过一个php jit'er自动完成。我认为在生成二进制可执行文件时,很难将一对post_inc/free操作码自动更改为一个pre_inc操作码。此外,php jit'er可以应用循环展开也不是一个奇迹。这只是一个优化的开始!

    希望会有一个吉特在 PHP 8.0