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

Недавно я для себя открыл, что 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 и будет всем счастье!

Добавить в закладки

Связанные записи

25
Сен
2009

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

  1. Vladimir says:

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

  2. Vladimir says:

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

  3. Сергей М. says:

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

    • Vladimir says:

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

      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.

Подписаться на RSS-ленту комментариев к статье «PHP: красота кода сказывается на производительности» Trackback URL: http://blog.sjinks.org.ua/php/650-php-code-beauty-impacts-performance/trackback/

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

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

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

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