bytecode — микрооптимизация интерпретатора PHP в коде

Спотыкаясь на это так нить Я решил написать аналогичный тест на 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

И вообще:

введите описание изображения здесь

где n — количество сумм в цикле. Чтобы быть полезной для нас формулой — нам нужно вычислить предел ее, когда N приближается к бесконечности (чтобы воспроизвести ситуацию, которую мы делаем МНОГО суммирований в цикле). Так :

введите описание изображения здесь

Таким образом, мы получаем вывод, что в оптимизированном коде должно быть 50% меньше умножений.

Вопросы

  1. Почему интерпретатор PHP не применяет оптимизацию кода?
  2. Почему фактор ускорения PHP — это только половина того, что в Java?

1

Решение

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

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

  1. Кажется, что $i++ это не то же самое, что ++$i с точки зрения кодов операций и использования памяти. Заявление $ i ++; генерирует коды операций:
POST_INC ~ 4! 1
БЕСПЛАТНО ~ 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";
}

}

Результаты

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

введите описание изображения здесь

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

ожидания

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

Надеюсь, что JIT’er будет в PHP 8.0

0

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]