PHP: красота кода сказывается на производительности
Читабельный код выполняется медленнее
Недавно я для себя открыл, что PHP не умеет оптимизировать код, а тут новый удар: оказывается, что красота кода отрицательно влияет на производительность.
Как и в прошлый раз, для проверки гипотез использовался Vulcan Logic Disassembler.
Сравнивались два куска кода:
Тест №1:
$a = true;
if (true == $a) {
print 1;
}
?>
и абсолютно аналогичный по функциональности
Тест №2:
$a = true;
if ($a) {
print 1;
}
?>
На выходе получилась такая картина.
Для первого случая:
-------------------------------------------------------------------------------
2 0 FETCH_CONSTANT ~0 'true'
1 ASSIGN $a, ~0
3 2 FETCH_CONSTANT ~2 'true'
3 IS_EQUAL ~3 ~2, $a
4 JMPZ ~3, ->8
4 5 PRINT ~4 1
6 FREE ~4
5 7 JMP ->8
6 8 RETURN 1
9* ZEND_HANDLE_EXCEPTION
Для второго случая:
-------------------------------------------------------------------------------
2 0 FETCH_CONSTANT ~0 'true'
1 ASSIGN $a, ~0
3 2 JMPZ $a, ->6
4 3 PRINT ~2 1
4 FREE ~2
5 5 JMP ->6
6 6 RETURN 1
7* ZEND_HANDLE_EXCEPTION
Видим, что в первом случае PHP, как мы его и попросили, честно сравнил true c $a и проверил, равен ли результат сравнения нулю или нет. Во втором же случае PHP просто проверил, равно ли $a нулю, сэкономив два опкода.
Будучи любопытным, я заменил проверку на равенство true проверкой на неравенство false. Хрен редьки не слаще: те же 9 опкодов:
-------------------------------------------------------------------------------
2 0 FETCH_CONSTANT ~0 'true'
1 ASSIGN $a, ~0
3 2 FETCH_CONSTANT ~2 'false'
3 IS_NOT_EQUAL ~3 ~2, $a
4 JMPZ ~3, ->8
4 5 PRINT ~4 1
6 FREE ~4
5 7 JMP ->8
6 8 RETURN 1
9* ZEND_HANDLE_EXCEPTION
Попутно выяснил, что те, кто говорят, что echo быстрее, чем print, правы. Я поменял print 1; на echo 1, в результате код программы сократился на один опкод:
-------------------------------------------------------------------------------
2 0 FETCH_CONSTANT ~0 'true'
1 ASSIGN $a, ~0
3 2 FETCH_CONSTANT ~2 'false'
3 IS_NOT_EQUAL ~3 ~2, $a
4 JMPZ ~3, ->7
4 5 ECHO 1
5 6 JMP ->7
6 7 RETURN 1
8* ZEND_HANDLE_EXCEPTION
Затем я решил проверить, что лучше: switch или спагетти из if/elseif.
Тест №3
$a = 5;
if ($a == 1) {
echo 1;
}
elseif ($a == 2) {
echo 2;
}
elseif ($a == 3) {
echo 3;
}
else {
echo ":-(";
}
?>
Результат:
-------------------------------------------------------------------------------
2 0 ASSIGN $a, 5
3 1 IS_EQUAL ~1 $a, 1
2 JMPZ ~1, ->5
4 3 ECHO 1
5 4 JMP ->14
6 5 IS_EQUAL ~2 $a, 2
6 JMPZ ~2, ->9
7 7 ECHO 2
8 8 JMP ->14
9 9 IS_EQUAL ~3 $a, 3
10 JMPZ ~3, ->13
10 11 ECHO 3
11 12 JMP ->14
13 13 ECHO ':-('
15 14 RETURN 1
15* ZEND_HANDLE_EXCEPTION
Тест №4
$a = 5;
switch ($a) {
case 1:
echo 1;
break;
case 2:
echo 2;
break;
case 3:
echo 3;
break;
default:
echo ":-(";
break;
}
?>
Результат:
-------------------------------------------------------------------------------
2 0 ASSIGN $a, 5
4 1 CASE ~1 $a, 1
2 JMPZ ~1, ->6
5 3 ECHO 1
6 4 BRK 1
7 5* JMP ->8
6 CASE ~1 $a, 2
7 JMPZ ~1, ->11
8 8 ECHO 2
9 9 BRK 1
10 10* JMP ->13
11 CASE ~1 $a, 3
12 JMPZ ~1, ->16
11 13 ECHO 3
12 14 BRK 1
13 15* JMP ->17
16 JMP ->20
14 17 ECHO ':-('
15 18 BRK 1
16 19* JMP ->21
20 JMP ->17
17 21 RETURN 1
22* ZEND_HANDLE_EXCEPTION
Результат для switch хоть и более читаемый, но оказался в полтора раза хуже результата для if. Sad but true.
После этого я собрал (с поддержкой оптимизатора) и поставил eAccelerator и повторил тесты.
Тест №1 и тест №2 дали одинаковый результат:
-------------------------------------------------------------------------------
2 0 ASSIGN $a, true
3 1 JMPZ $a, ->3
4 2 ECHO 1
6 3 RETURN 1
4* ZEND_HANDLE_EXCEPTION
Что характерно, eAccelerator самостоятельно преобразовал print в echo. Лапочка.
Для теста №3 результат остался неизменным: там, собственно, нечего оптимизировать.
А результаты теста №4 впечатлили:
4 1 CASE ~0 $a, 1
2 JMPZ ~0, ->5
5 3 ECHO 1
17 4 RETURN 1
7 5 CASE ~0 $a, 2
6 JMPZ ~0, ->9
8 7 ECHO 2
17 8 RETURN 1
10 9 CASE ~0 $a, 3
10 JMPZ ~0, ->13
11 11 ECHO 3
17 12 RETURN 1
14 13 ECHO ':-('
17 14 RETURN 1
15* ZEND_HANDLE_EXCEPTION
На выходе получили результат, который по эффективности аналогичен коду из if!
Поэтому в заключение скажу: ставьте eAccelerator и будет всем счастье!
Связанные записи
Автор: Vladimir; опубликовано в: PHP; метки: eAccelerator, PHP, оптимизация, производительностьСен
2009


Меня зовут Владимир, я программист-фрилансер, специализирующийся на Web-программировании и програмировании под Linux.
По совместительству занимаюсь администрированием LAMP/LNMP-серверов и техническим переводом.






Одно обидно: из eAccelerator 0.9.6-rc1 выкинули оптимизатор и кучу всего остального, оставив только кэширование опкода.
Интересно протестировать связку APC + optimizer.
Ну спички же форменные. Да и как это будет сказываться на “производительности” самого интерпретатора?
Спички спичками, но есть несколько нюансов:
Вообще мне на ум приходит сравнение программ, скомпилированных Turbo Pascal 7.0 и Borland C++ 3: в Turbo Pascal оптимизатор отсутствует в принципе, а в BC++ его можно включить. Разница ощутима.
Я натравил оптимизатор на устаревшую локальную копию своего блога (интерпретатор обломался где-то после загрузки плагинов) и сравнил размер выдачи VLD:
Разница (для не полностью загруженного WP) составляет 3,421 опкод. Полагая, что опкод выполняется 0.5 микросекунды (думаю, что для дешевых VPS это где-то рядом), имеем разницу в 1.7 миллисекунды. На полностью загруженном WordPress разница будет видна сильнее. Использование оптимизатора — бесплатный способ снизить нагрузку на сервер.
Положительно. Компиляция и оптимизация будут выполнены один раз, после чего результат будет закэширован в shared memory.
[...] « PHP: красота кода сказывается на производительности [...]
Почему вы считаете, что
5 PRINT
6 FREE
работает медленнее
5 ECHO
?
в остальных рассуждениях подобный косяк повторяется.
Очевидно потому, что копался во внутренностях Zend Engine.
Во-первых, лишний опкод производительности не добавляет (из-за издержек на его анализ/выполнение).
Во-вторых, внутренняя реализация
printвызываетecho, из-за чегоprintне может быть быстрее:{
zend_op *opline = EX(opline);
Z_LVAL(EX_T(opline->result.u.var).tmp_var) = 1;
Z_TYPE(EX_T(opline->result.u.var).tmp_var) = IS_LONG;
return ZEND_ECHO_SPEC_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);
}
В-третьих, оптимизаторы неспроста
PRINT/FREEпреобразовывают вECHO.Вы меня не поняли и забавно реагируете.
Никто не утверждал, что лишний опкод добавляет производительность, но вовсе не факт, что print + free выполняется медленнее, чем echo. Я пишу о том, что рассуждение о скорости глядя на внутренние опкоды php, которые вызывают функции, без приведения листингов этих функций бессмысленно. Вот еще пример:
“Конкатенация или переменные внутри строки”
CONCAT
CONCAT
и
INIT_STRING
ADD_STRING
ADD_VAR
ADD_STRING
Можно предположить, что второй блок выполняется медленне, но утверждать этого нельзя.
Приведённая цитата на Си вообще непонятно, как относится к вопросу.
Про оптимизаторов довод единственно-хороший, но я впервые слышу это утверждение, а никаких ссылок в вашем сообщении нет.
Ну почему же? Внутренняя реализация
printвызывает внутреннюю реализациюecho. Уже только поэтомуprintне может быть быстрее. Ну и еще необходимость выполненияFREE.Эм… Листинг обработчиков опкодов занимают порядка мегабайта. На мой взгляд, приводить их несколько нецелесообразно. Тем более, что в них интенсивно используются внутренние макросы Zend Engine, которые не всегда очевидны.
Если Вы познакомитесь с Zend Engine поближе, то можно
INIT_STRINGвыделяет один байт памяти и создаёт строковую переменную.ADD_STRINGвызывает функциюadd_string_to_string().ADD_VARпреобразует переменную в строку и вызывает ту жеadd_string_to_string().CONCATвызывает функциюconcat_function(), которая по функциональности эквивалентнаADD_VAR(с той разницей, чтоconcat_function()не производит действий, характерных для обработчика опкода).Что
concat_function(), чтоadd_string_to_string()— это, по сути,erealloc()+memcpy().В первом случае получаем два распределения памяти и копирования, во втором — три (это не считая выделения однобайтового буфера). Плюс во втором случае получаем два лишних опкода, которые, к тому же, занимают место в памяти (причём нельзя сказать, что очень мало).
Видите? Если бы я приводил код всех обработчиков, было бы мало смысла
Та цитата показывает, что обработчик
PRINTсоздаёт целочисленную переменную (возвращаемое значение) и вызывает обработчикECHO. Но с используемыми именами макросов это не очевидноВсё-таки Вы невнимательно читали:
Ну и плюс листинги.
[...] оптимизатор, встроенный в eAccelerator, то можно повысить производительность PHP-кода. Анализ производительности MySQL-запросов и знание [...]