Микро-оптимизация интерпретатора PHP в коде - PullRequest
0 голосов
/ 01 декабря 2018

Споткнувшись об этом так что нить я решил написать аналогичный тест на 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 не оптимизирует байт-кодкак JVM в Java
  2. Даже когда я оптимизировал код вручную - я получил ускорение ~ 8%, когда версия Java получила ~ 16% ускорение.Таким образом, версия PHP получает примерно половинный коэффициент ускорения по сравнению с кодом Java.

Обоснование оптимизации

Я не буду вдаваться во многие детали, но соотношение умножений в-оптимизированный код ->

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 Ответ

0 голосов
/ 05 декабря 2018

Пришло время проанализировать коды операций PHP, которые генерируются интерпретатором PHP.Для этого вам нужно установить расширение VLD и использовать его из командной строки для генерации операционных кодов php-скрипта под рукой.

Анализ кода операции

  1. Кажется, что $i++ не совпадает с ++$i с точки зрения кодов операций и использования памяти.Оператор $ i ++;генерирует коды операций:
 POST_INC ~4 !1
 FREE     ~4

Увеличивает счетчик на 1 и сохраняет предыдущее значение в слоте памяти # 4.Тогда, потому что это значение никогда не используется - освобождает его от памяти.Вопрос - зачем нам хранить значение, если оно никогда не используется?

Кажется, что действительно есть штраф за цикл, поэтому мы можем повысить производительность, выполнив развертывание цикла .

Оптимизированный тестовый код

Изменение 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";
}

}

Results

Скрипт агрегирует количество попаданий ЦП в один или другойзначение ускорения.Когда ЦП и Ускорение отображаются в виде графика, мы получаем такую ​​картину:

enter image description here

Таким образом, наиболее вероятно, что сценарий получит ускорение на 10%.Это означает, что наша оптимизация привела к ускорению + 2% (по сравнению с оригинальными сценариями 8%).

Ожидания

Я почти уверен, что все эти вещисделано - может быть сделано автоматически с помощью PHP JIT'er.Я не думаю, что сложно автоматически изменить пару кодов операций POST_INC / FREE в один код операции PRE_INC при создании двоичного исполняемого файла.Также не чудо, что PHP JIT'er может применить развертывание цикла.И это только начало оптимизаций!

Надеемся, что JIT'er будет в PHP 8.0

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...