PHP-оптимизация

При разработке больших проектов постепенно встает вопрос об оптимизации кода проекта: насколько оправдан расход памяти, каким образом можно увеличить скорость выполнения написанного php-кода. Поначалу многие не задумываются о правильности и эффективности написанного кода, пишут по принципу: работает – да и ладно. Несмотря на то, что интерпретатор PHP довольно быстро выполняет php-код, и есть множество узких мест, замедляющих скорость выполнения кода, которые находятся вне PHP, оптимизация php-кода также занимает важное место, и оптимизацию кода необходимо применять уже в начале процесса написания кода.

Большинство сценариев PHP выполняют простые действия. Стандартное поведение сценария – загрузка небольшого количества информации от пользователя, получение некоторой информации из базы данных или файла, вывод соответствующего HTML и отправка результата работы клиенту. Здесь, первым делом нужно понять, что именно должно стать результатом оптимизации: быстродействие, удобство масштабирования, уменьшение количества используемых ресурсов сервера, уменьшение времени передачи данных или все вместе. В последнем случае, необходимо не только найти все критические участки, но и сбалансировать их оптимизацию.

Приведу простейший пример, пусть на сервере, имеющем 100 Мб свободной оперативной памяти, находятся два скрипта, результат работы которых одинаков. Первый скрипт оптимизирован на максимальное быстродействие, требует 10 Мб памяти и получает данные из файла путем полного его прочтения, второй – на минимальный расход памяти, требует 5 Мб памяти и получает данные из того же файла по частям. В результате одного запроса, первый скрипт выполнится быстрее второго, но если будет более десяти запросов одновременно, именно скорость работы второго скрипта станет более высокой. Почему же так происходит? В первом скрипте узким местом является использование ресурсов памяти, во втором – особенности системы ввода-вывода. После расхода первым скриптом всей доступной оперативной памяти, система перейдет к использованию виртуальной памяти, при этом, дополнительным узким местом этой схемы станет та же система ввода-вывода.

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

Я не буду здесь рассматривать оптимизацию операционной системы, оптимизация настроек сервера и т.п., т.к. большинство веб-мастеров пользуется хостингом и, соответственно, не сможет самостоятельно все настроить. Здесь будет рассмотрена только оптимизация php-кода. Следует отметить, что в каждом конкретном случае, некоторые виды оптимизации будут полезны, другие – будут напрасной тратой времени и сил. Часто полезность усовершенствования кода будет пренебрежимо мала. Возможно, со временем, внутренние изменения в PHP сделают успешную оптимизацию бесполезной или даже вредной.

Ниже перечислены основные действия по повышению производительности для PHP 5 версии:

Действия по оптимизации расхода оперативной памяти:

  • Анализ результатов работы ваших функций. Перед написанием функции проверьте, не существует ли стандартный аналог.
  • Освобождение памяти в случае окончания использования больших массивов или объектов только в глобальной области видимости (в локальной области видимости память будет освобождена автоматически). Обратите внимание, что функция unset() удаляет переменную из области видимости и, только в случае отсутствия на объект других ссылок, освобождает занимаемую объектом память. Присвоение переменной значения null всегда уничтожает объект и освобождает занимаемую объектом память, независимо от того, имеются ли ещё ссылки на этот объект. При этом переменная не будет удалена из области видимости, т.е. фактически переменная будет содержать неопределенное (нулевое) значение и, соответственно, занимать память на содержание этой переменной (порядка 72 байт).
  • Анализ оправданности использования ООП (объектно-ориентированного программирования). Перед написанием объектно-ориентированного кода, задайте себе два вопроса: «нужен ли здесь объектно-ориентированный подход?» и «могу ли я писать объектно-ориентированный код?». Например, определение статической функции внутри класса увеличивает объем памяти, необходимой только для содержания этой функции, на 10-18%. Использование в качестве структуры массива, а не класса, также позволяет сэкономить память. Возможно, будет выгоднее просто вынести функции в отдельный файл, а не реализовывать их в качестве методов класса.
  • Анализ возможности реализации статической версии метода в классе. Если метод не использует параметр $this, то он должен быть объявлен с использованием ключевого слова static.

Действия по увеличению скорости исполнения кода:

  • Анализ оптимизированности SQL-запросов. В большинстве проектов именно оптимизация SQL-запросов дает наибольшее увеличение производительности.
  • Использование буферизации вывода, всевозможных кеширующих модулей, позволяет увеличить производительность на 25%-100%.
  • Использование более коротких коротких имен для переменных, функций, констант и классов может повысить производительность до 20%. В то же время не забывайте о дальнейшей поддержке кода, говорящее имя функции намного удобнее при модификациях кода.
  • Проверка существования переменной (функция isset()) перед обращением к ней. Подавление ошибки, возникающей при обращении к несуществующей переменной, путем использования @ сильно снижает производительность.
  • Использование 'одинарных кавычек' позволяет интерпретировать код быстрее, т.к. в случае "двойных кавычек" внутри строки ведется поиск переменных
  • Анализ возможности выноса «лишних» функций из цикла. Например, замена функции count() на переменную, вычисленную до начала цикла и содержащую результат этой функций, в выражении for($i=0; $i<count($array);$i++) повысит производительность этого цикла. В противном случае функция count() будет вызываться и выполняться на каждой итерации цикла.
  • Использование оператора case вместо множественного использования конструкции if...else.
  • Использование явного обращения к полям массива. Обращение вида $array['id'] выполняется в 7 раз быстрее, чем обращение $array[id]. Кроме того, это защищает от ошибок при дальнейшей поддержке скрипта, т.к. в один прекрасный день может появиться константа с именем id.
  • Использование дополнительной переменной, содержащей ссылку на конечный массив, при обработке многомерных массивов в цикле. Для ускорения цикла for($i = 0; $i < 5; $i++) $a['b']['c'][$i] = func($i);, до начала цикла возможно записать следующую инструкцию $item =p$a['b']['c'] и переписать цикл так: for($i = 0; $i < 5; $i++) $ref[$i] = $i;.
  • Использование модулей Apache mod_gzip и mod_deflate позволяет сократить трафик, за счет чего увеличится скорость загрузки страниц.

Следующие действия по оптимизации кода повышают быстродействие только в случае многократного использования:

  • Использование ++$i вместо $i++ в циклах дает прирост производительности в 6%.
  • Использование "двойных кавычек" для конкатенации (склеивания) переменных. Инструкция вида $s="$s1$s2$s3" интерпретируется быстрее, чем $s=$s1.$s2.$s3. Это утверждение справедливо только для трех и более переменных.
  • Использование полных путей в инструкциях include и require позволит тратить меньшее время на поиск системой реального пути.
  • Закрытие открытых коннектов к базе данных после того как необходимость в них отпадает. В то же время не следует много раз подключаться к одной и той же базе данных.
  • Анализ возможности замены include() и include_once() на require() и require_once() соответственно.
  • Использование HTML-вставок в код, вместо вывода значительного объема oстатическихo строк (не содержащих результатов работы кода). Вообще, скорость выдачи статической страницы (HTML), в несколько раз быстрее выдачи страницы написанной на PHP. Но здесь не стоит увлекаться, т.к. ввод в интерпретатора в режим обработки PHP и вывод из него также нагружают сервер.
  • Анализ возможности замены функций preg_replace и str_replace в некоторых случаях. Функция str_replace работает быстрее, чем preg_replace, и в тоже время функция strtr быстрее функции str_replace. Также, использование строковых функций strncasecmp, strpbrk и stripos более оптимально, чем использование регулярных выражений. Однако, вместо вложенности этих функций, следует использовать именно функции регулярных выражений.
  • Использование явной инициализации переменных. Например, инкремент неинициализироанной переменной в 9-10 раз медленнее, чем предварительно инициализированной. Кроме того, при явной инициализации переменных возникает меньше ошибок.
  • В качестве заключения хотелось бы отметить, что использование конструкции echo, вместо функции print, не дает ощутимого роста производительности.

Рекомендации по оценке производительности:

  • Для определения времени начала исполнения скрипта, вместо функций, возвращающих текущее время, предпочтительнее использование $_SERVER["REQUEST_TIME"].
  • Используйте профайлер для определения критических участков кода.

Перед оптимизацией быстродействия кода, я настоятельно рекомендую проверить оптимизацию SQL-запросов к базе данных, а также оптимизировать http-запросы, уменьшить размер js и css, подумать над кэшированием шаблонов, и только после этого заняться проверкой кода на производительность.

Хороший стиль программирования предполагает оптимизацию во время написания кода, а не латание дыр в последствии.