PHP: красота кода сказывается на производительности

Читабельный код выполняется медленнее

Недавно я для себя открыл, что PHP не умеет оптимизировать код, а тут новый удар: оказывается, что красота кода отрицательно влияет на производительность.

Как и в прошлый раз, для проверки гипотез использовался Vulcan Logic Disassembler.

Сравнивались два куска кода:

Тест №1:

[-]
View Code PHP
<?php
    $a = true;
    if (true == $a) {
        print 1;
    }
?>

и абсолютно аналогичный по функциональности

Тест №2:

[-]
View Code PHP
<?php
    $a = true;
    if ($a) {
        print 1;
    }
?>

На выходе получилась такая картина.
Для первого случая:

[-]
View Code Text
line     #  op                           fetch          ext  return  operands
-------------------------------------------------------------------------------
   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

Для второго случая:

[-]
View Code Text
line     #  op                           fetch          ext  return  operands
-------------------------------------------------------------------------------
   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 опкодов:

[-]
View Code Text
line     #  op                           fetch          ext  return  operands
-------------------------------------------------------------------------------
   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, в результате код программы сократился на один опкод:

[-]
View Code Text
line     #  op                           fetch          ext  return  operands
-------------------------------------------------------------------------------
   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

[-]
View Code PHP
<?php
    $a = 5;
    if ($a == 1) {
        echo 1;
    }
    elseif ($a == 2) {
        echo 2;
    }
    elseif ($a == 3) {
        echo 3;
    }
    else {
        echo ":-(";
    }
?>

Результат:

[-]
View Code Text
line     #  op                           fetch          ext  return  operands
-------------------------------------------------------------------------------
   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

[-]
View Code PHP
<?php
    $a = 5;
    switch ($a) {
        case 1:
            echo 1;
            break;
        case 2:
            echo 2;
            break;
        case 3:
            echo 3;
            break;
        default:
            echo ":-(";
            break;
    }
?>

Результат:

[-]
View Code Text
line     #  op                           fetch          ext  return  operands
-------------------------------------------------------------------------------
   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 дали одинаковый результат:

[-]
View Code Text
line     #  op                           fetch          ext  return  operands
-------------------------------------------------------------------------------
   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 впечатлили:

[-]
View Code Text
   2     0  ASSIGN                                                   $a, 5
   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, оптимизация, производительность
25
Сен
2009

RSS Комментарии к статье «PHP: красота кода сказывается на производительности» (10)  »

  1. Одно обидно: из eAccelerator 0.9.6-rc1 выкинули оптимизатор и кучу всего остального, оставив только кэширование опкода.

  2. Интересно протестировать связку APC + optimizer.

  3. Ну спички же форменные. Да и как это будет сказываться на “производительности” самого интерпретатора?

    • Спички спичками, но есть несколько нюансов:

      1. Я работаю над проектом в котором одна из задач — массовая рассылка писем подписчикам. Каждое письмо «персонализируется» и т.п., затем отсылается. Подписчиков много тысяч. Слабое место отнюдь не почтовик, а PHP. Он реально тормозит. Множество спичек в цикле, выполняющемся десятки тысяч раз — это очень ощутимо. Говорю потому, что снёс xCache и поставил eAccelerator и включил оптимизатор. Рейт отправки вырос на 30%.
      2. На хорошо нагруженных сайты типа littlefox.ru работа оптимизатора очень заметна — по падению Load Average и сниженной нагрузки на процессор. Железо на сервере не самое мощное, но оптимизация реально творит чудеса (я говорю не только про спички — пришлось написать пару плагинов и даже расширений PHP, чтобы компенсировать тормоза WordPress).

      Вообще мне на ум приходит сравнение программ, скомпилированных Turbo Pascal 7.0 и Borland C++ 3: в Turbo Pascal оптимизатор отсутствует в принципе, а в BC++ его можно включить. Разница ощутима.

      Я натравил оптимизатор на устаревшую локальную копию своего блога (интерпретатор обломался где-то после загрузки плагинов) и сравнил размер выдачи VLD:

      • Без оптимизатора: 184,128 строк
      • С оптимизатором: 180,707 строк

      Разница (для не полностью загруженного WP) составляет 3,421 опкод. Полагая, что опкод выполняется 0.5 микросекунды (думаю, что для дешевых VPS это где-то рядом), имеем разницу в 1.7 миллисекунды. На полностью загруженном WordPress разница будет видна сильнее. Использование оптимизатора — бесплатный способ снизить нагрузку на сервер.

      Да и как это будет сказываться на “производительности” самого интерпретатора?

      Положительно. Компиляция и оптимизация будут выполнены один раз, после чего результат будет закэширован в shared memory.

  4. leo

    Почему вы считаете, что

    5 PRINT
    6 FREE

    работает медленнее

    5 ECHO

    ?

    в остальных рассуждениях подобный косяк повторяется.

    • Очевидно потому, что копался во внутренностях Zend Engine.

      Во-первых, лишний опкод производительности не добавляет (из-за издержек на его анализ/выполнение).
      Во-вторых, внутренняя реализация print вызывает echo, из-за чего print не может быть быстрее:

      [-]
      View Code C
      static int ZEND_PRINT_SPEC_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
      {
          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.

      • leo

        Вы меня не поняли и забавно реагируете.
        Никто не утверждал, что лишний опкод добавляет производительность, но вовсе не факт, что print + free выполняется медленнее, чем echo. Я пишу о том, что рассуждение о скорости глядя на внутренние опкоды php, которые вызывают функции, без приведения листингов этих функций бессмысленно. Вот еще пример:
        “Конкатенация или переменные внутри строки”

        CONCAT
        CONCAT

        и

        INIT_STRING
        ADD_STRING
        ADD_VAR
        ADD_STRING

        Можно предположить, что второй блок выполняется медленне, но утверждать этого нельзя.

        Приведённая цитата на Си вообще непонятно, как относится к вопросу.
        Про оптимизаторов довод единственно-хороший, но я впервые слышу это утверждение, а никаких ссылок в вашем сообщении нет.

        • но вовсе не факт, что print + free выполняется медленнее, чем echo

          Ну почему же? Внутренняя реализация 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 и повторил тесты. Тест №1 и тест №2 дали одинаковый результат. Что характерно, eAccelerator самостоятельно преобразовал print в echo. Лапочка. Для теста №3 результат остался неизменным: там, собственно, нечего оптимизировать. А результаты теста №4 впечатлили.

          Ну и плюс листинги.

  5. [...] оптимизатор, встроенный в eAccelerator, то можно повысить производительность PHP-кода. Анализ производительности MySQL-запросов и знание [...]

Оставить комментарий к записи «PHP: красота кода сказывается на производительности»

Вы можете использовать данные тэги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Изображения должны быть включены!

Оставляя комментарий, вы выражаете своё согласие с Правилами комментирования.

Подписаться, не комментируя