...или 15 привычек, которые помогут ускорить PHP-приложение

Практически каждый программист стремится в своих приложениях не только максимально точно реализовать требуемый функционал, но и сделать это как можно более эффективным методом. Для этого конечно же необходимо проектирование, подходящий выбор используемых технологий, возможно некоторый опыт в предметной области, этот список можно продолжать достаточно долго, но я позволю себе этого не делать, так как речь сегодня пойдет не об этом. Вместо этого хочу обратить Ваше внимание на более простые и "приземленные" методы оптимизации PHP-кода, которые может быть и не так эффективны по сравнению с указанными выше, но зато не требуют каких-либо усилий со стороны кодера и/или программиста, достаточно лишь воспринимать их как "не вредные" привычки.

Прочитав достаточно солидный объем разного рода документации по PHP, я часто натыкался на статьи и тексты, так или иначе связанные с производительностью PHP-скриптов. Порой в такого рода источниках информации удавалось найти достаточно интересные и неочевидные факты об этом языке программирования, которые не смотря на свою простоту могли дать вполне заметный прирост к производительности итогового приложения. Я почему-то очень серьезно стал относиться к производительности написанных мной скриптов, и довольно часто стал испытывать на практике спорные моменты в реализации, о которых узнавал из Сети или каких-либо других источников, с помощью самописных или opensource benchmark'ов, хотя порой и просто внедряя в реальные приложения. Как ни странно, в большинстве случаев практика подтверждала теорию, и я стал постоянно пользоваться этими простыми правилами, о которых я и хочу Вам рассказать.

Повышения значения индекса с помощью ++\$i;

Этот факт был наверное одним из самых удивительных для меня, когда я впервые о нем услышал, но действительно операция ++\$i; выполняется несколько быстрее, чем \$i++;. или другие вариации на ту же тему вроде \$i+=1;. Привычка использовать в качестве индекса цикла переменную под названием i, казалось бы стара как Мир, мне она досталась в наследство от C, а в месте с ней "в комплекте" шла привычка писать выражение i++ в заголовках циклов. Разница в скорости обработки этих выражений, насколько мне известно, обусловлена разным количеством элементарных машинных операций, которые необходимо выполнить процессору (в точных цифрах не уверен, пишу по памяти, но ++\$i; требует трех элементарных операций, а \$i++;– четырех). В справедливости этого факта не трудно убедиться, достаточно написать простенький скрипт, состоящий из цикла с достаточно большим количеством итераций, и замерить любым способом точное время его выполнения при использовании разных способов инкрементации индекса цикла.

Вывод статического контента без помощи PHP

Сейчас тот факт, что использование интерпретатора PHP для вывода статического контента сильно замедляет этот процесс, кажется мне очевидным, но поначалу я использовал echo там, где он был необходим, ничуть не чаще, чем там, где он лишь замедляет работу скрипта. От использования еще менее эффективного способа - print, меня избавила моя лень: писать каждый раз на одну букву больше дико не хотелось (в отличии от echo, print возвращает информацию об успешности выполнения своей работы, что в большинстве случаев просто-напросто не нужно). Проверить опять же не трудно - нужен лишь объемистый текстовый файл, который достаточно вывести в browser разными способами и засечь уходящее на это время.

Вывод статического контента из отдельного файла

Частенько при желании выполнить указанное в заголовке действие по привычке используют include, require или их _once версии, что является далеко не самой лучшей идеей с точки зрения производительности. Самым быстрыми быстрыми и экономичными поотношению к оперативной памяти являются функции readfile и fpassthru. В качестве доказательства этого факта приведу таблицу, демонстрирующую статистику выполнения этой операции различными методами и позаимствованную с одного англоязычного сайта:

Функция Время (сек.) Оперативная память (байт)
32Kb файл 1Mb файл 32Kb файл 1Mb файл
file\_get\_contents 0.00152 0.00564 52480 1067856
fpassthru **0.00117** 0.00184 20016 20032
fgets 0.00195 0.07190 30760 30768
file 0.00157 0.06464 87344 2185624
require\_once 0.00225 0.08065 67992 2067696
readfile **0.00117** 0.00191 **19192** 19208

Вывод переменных

Наверняка вам известно, что переменные можно выводить с помощью конструкции вроде echo"\$vartext";, что является одним из самых удобных вариантов решения этой задачи благодаря минимальному количеству символов, которые необходимо набрать, но с точки зрения быстродействия этот вариант далек от идеала, так как влечет за собой достаточно серьезные преобразования в памяти сервера, эффект которых порой бывает заметен невооруженным глазом. Частично ущерб производительности можно сгладить заменой этой конструкции на echo\$var." text";, что приводит к несколькому усложнению внешнего вида кода и несколько поправляет ситуацию со скоростью выполнения. Но как известно знак . обозначает конкатенацию двух строк, что тоже требует некоторых вычислений и затрат памяти, но и от нее можно избавиться, заменив на запятую. Выражение echo\$var," text"; ничем по своему эффекту не отличается от предложенных ранее вариантов, за исключением максимального быстрого выполнения, обусловленного отсутствием дополнительных преобразований в процессе передачи просто последовательности из константы и переменной.

Избегайте выполнения лишних действий

Достаточно абстрактное утверждение, но тем не мение постоянное напоминание себе о нем может избавить Вас от совершения массы ошибок. Самой широкораспространенной является наверное вызов какой-либо функции (чаще всего count(); или strlen();) в проверке условия выхода из цикла. Когда-нибудь доводилось писать видеть в собственном или чужом коде выражение вида for(\$i = 0; \$i \<count(\$array); ++\$i) { ... }? А задумываться о последовательности выполнения действий при его обработке? Стоит только немного начать размышлять и ошибка становится очевидной: count(); выполняется при каждой итерации цикла, что приводит к подсчету количества элементов массива при каждой проверки условия выхода из цикла - почему бы не посчитать это значение заранее и сравнивать значения индекса с переменной, а не с результатом выполнения функции?

@

Использование этого оператора стоит избегать при каждой возможности. Казалось бы такое простое действие, как сокрытие вывода возможного сообщения об ошибке, влечет за собой достаточно трудоемкую последовательность действий: устанавливает значение параметра PHP-интерпретатора error_reporting = 0, выполняет указанное за этим оператором действие, возвращает значение error_reporting в исходное состояние.

Маленькие мелочи

Развивая тему предыдущего подраздела, хочется обратить внимания, что даже на еще более элементарных вещах можно сэкономить драгоценное процессорное время:

  • Вместо условия if(\$variableOne == \$variableTwo) { ... } можно написать if(\$variableOne === \$variableTwo) { ... }, что избавит от проверки на соответствие типов данных и приведения их друг к другу, в некоторых случаях эти действия эти случаях эти действия конечно же и бывают необходимы, но бывает это далеко не часто.
  • Глядя на выражения вроде if(\$boolean == true) { ... }, я чаще всего вспоминаю цитату из одного малоизвестного интернет-ресурса: if (b.ToString().length \< 5) { ... }. Хоть и не имет никакого отношения к PHP, но суть проблемы отражает очень ярко.
  • Самым очевидным способом проверить попадает ли длина строки в какой-либо диапазон является использование функции strlen(); и сравнение полученного результата с фиксированными значениями, но зачем выполнять лишний вызов функции, если можно воспользоваться услугами конструкцией языка PHP isset(); для определения наличия в строке определенных символов. if(isset(\$str{5})) { ... } приведет к абсолютно тем же результатам, что и if(strlen(\$str)>4){ ... }
  • Битовые операции выполняются намного быстрее относительно обычных арифметических действий. Об этом факте редко вспоминают, да и работать с ними умеет далеко не каждый, но порой они бывают очень актуальны, особенно при частой работе с числами кратными двойке.
  • Угадайте, что делает интерпретатор при виде надписи 1/2? Правильно: делит 1 на 2. Зачем лишний раз утруждать его, когда можно написать просто половину - 0.5.
  • При возвращении значения переменной из функции при помощи global выполняется на порядок больше действий, чем при классическом return.
  • Конечно же фраза \$array[text]; интерпритируется практически точно так же, как и \$array['text'];, но зачем выполнять лишнее преобразование из необъявленной константы в строку, проверять, что такой константы все же не существует, выводить сообщение типа E_NOTICE, если можно всего этого не делать?
  • По возможности не используйте require_once(); или include_once(); неоднократно по отношению к одному и тому же файлу. При отсутствии какого-либо эффекта, попусту тратится время на обработку повторного запроса.
  • Даже "безобидных" ошибок стоит избегать, лишняя проаерка потратит не так много процессорного времени, как генерирование достаточно длинного сообщения об ошибке и вывод его в stdout, stderr или лог-файл, а также не стоит забывать, что даже "безобидные" ошибки могут стать потенциальной угрозой безопасности приложения вцелом.

В заключении...

...хотелось бы упомянуть одну из первых статей по оптимизацииPHP, которые мне доводилось читать, до сих пор храню ссылку на нее в bookmark'ах, именно она и выступала в роли одного из основных источников информации для этого текста. В качестве возможных вариантов продолжения чтения про PHP хотелось бы предложить Вам соответствующие раздел сайта, серию статей, тэг и RSS-ленту.

07 февраля 2008 |  Иван Блинков  |  PHP