Оптимизация PHP

Существует ряд правил, которые стоит соблюдать, чтобы увеличить скорость работы приложений на PHP. Правила простые и не потребуют значительных изменений в существующих приложениях. Материал охватывает нюансы кода и системные настройки PHP. Несколько простых действий позволят оптимизировать PHP приложение в несколько раз.

Fastcgi

FastCGI — это один из вариантов подключения PHP к Web серверу. Лучше всего использовать в связке с Nginx. PHP-fpm (Fastcgi контейнер для PHP) и Nginx по умолчанию поддерживают совместную работу и очень легко настраиваются.

OpCache

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

Именно это и делает модуль opCache. Результат первой компиляции будет сохранен в кэш, с которым и будет работать PHP. Таким образом это ускорит выполнение за счет отсутствия тяжелого процесса компиляции. Когда файлы изменяются, модуль сам сбросит кэш и обеспечит перекомпиляцию. Короче, этот модуль делает очень полезную экономию ресурсов даже без необходимости его как-то настраивать.

В версии PHP5.5+ этот модуль поставляется в стандартной сборке. В предыдущих версиях модуль нужно устанавливать самостоятельно. Проверить наличие можно так:

php -i | grep opcache

# Пустой вывод будет означать, что модуля нет

Если версия слишком ранняя, лучше использовать APC:

apt-cache search php-apc

# Это альтернатива opCache, но делает то же самое

Кэширование

Часто код просто медленный. Например, обращения к внешним API, тяжелые выборки из баз данных, обработка больших файлов и т.п. В этом случае, кэширование данных следует использовать, как средство оптимизации.

Для PHP наиболее популярным решением для кэширования является Memcache. Очень простой в использовании и очень быстрый, т.к. поддерживает только самое необходимое.

PHP.ini

Если Вы только установили PHP, убедитесь, что Вы настроили наиболее важные параметры под Ваш сайт. Это также может сэкономить ресурсы:

  • memory_limit = 32M — не стоит устанавливать этот параметр слишком большим. Увеличивайте его только в крайних случаях.
  • zlib.output_compression = Off, zlib.output_compression_level = -1 компрессию лучше использовать на стороне Web сервера.
  • max_execution_time = 5 — максимальное время работы скрипта не должно быть больше 5 секунд. Увеличивайте только в крайних случаях.
  • zend.enable_gc = On — включает сборщик мусора (будет оптимизировать память на фоне).
  • expose_php = Off — PHP не будет отправлять свою версию вместе с ответом.
  • report_memleaks = On — будет отправлять в лог ошибок информацию об обнаруженных утечках памяти.
  • post_max_size = 4M, upload_max_filesize = 4M — настройте максимальный размер запросов и файлов для загрузки. Защитит Вас от обработки громадных запросов, которых не должно быть на Вашем сайте.

Сессии

По умолчанию, PHP хранит сессии в файлах. Это довольно эффективное решение. Но когда файлов становится очень много (десятки тысяч), работа с ними будет замедляться в рамках одной папки (особенности файловых систем). В этом случае лучше перенести сессии на Memcache (php.ini):

session.save_handler = memcache 
session.save_path = "tcp://localhost:11211"

# localhost:11211 это стандартный хост и порт Memcache

Оптимизация кода

ООП

Помните, ООП — это всегда очень медленно. Объекты нужно создавать, где-то хранить и уничтожать. Не используйте объекты, если они Вам не нужны. Например, тут:

<?
$post = new Post();
$post->set_title($_GET['title']);
$post->set_description($_GET['description']);
$post->save();

# Создаем объект только для того, чтобы сохранить данные в БД

или тут:

<?

# $posts = список объектов Post, полученных каким-то образом
foreach ( $posts as $post )
{
	echo $post->title . '<br/>';
}

# Используем список объектов только для того, чтобы вывести свойство

В этих примерах использование ООП не имеет особого смысла. Зато расходует ресурсы. Старайтесь использовать массивы, когда объекты не нужны:

<?
mysql::insert(['title' => $_GET['title'], 'description' => $_GET['description']]);

# Избежали создания объекта, функция просто сохраняет данные из массива в базу

или тут:

<?
$posts = mysql::query('SELECT title FROM posts');
foreach ( $posts as $post )
{
	echo $post['title'] . '<br/>';
}

# Намного лучше сделать простую выборку и вывести нужные данные из массива

Мелочи

При работе с файлами, используйте абсолютные пути. Тогда не будут происходить лишние операции поиска файла:

<?
include 'file.php';
file_get_contents('dir/data.txt');
include '/var/www/file.php';
file_get_contents('/var/www/dir/data.txt');

Константы классов работают эффективнее, чем define:

<?
define('MAX_POSTS_PER_PAGE', 10);
class posts {
	const PER_PAGE = 10;
	...
}

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

<?
for ( $i = 0; $i < mysql::get_col('SELECT count(*) FROM posts'); $i++ )
{
...
}
for ( $i = 0, $max = mysql::get_col('SELECT count(*) FROM posts'); $i < $max; $i++ )
{
...
}

В качестве ключей массивов всегда указывайте строки с кавычками:

<?
$post[title] = 'Первый пост';
$post['title'] = 'Первый пост';

Используйте встроенные функции работы со строками вместо регулярных выражений, если это возможно.

<?
preg_match('/жопа/ui', $post['title']);
strpos($post['title'], 'жопа');

Используйте строки с одинарными кавычками:

<?
$post["title"] = "Почему?";
$post['title'] = 'Потому, что не происходит дополнительная обработка переменных'

PHP cron-скрипты

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

<?
while ( true )
{
	$rss = file_get_contents('http://somesite.com/rss');
	preg_match_all('/title>(.+?)<\/title/', $rss, $matches);
}

Переменная $matches передается по ссылке. Это значит, что с каждым новым повторением, она будет расти. Другой частый пример, это просто использование общего массива:

<?
while ( true )
{
	$rss = file_get_contents('http://somesite.com/rss');
	$has_something = preg_match('/title>(.+?)<\/title/', $rss);
	if ( $has_something ) $updates[] = time();

	$rss = file_get_contents('http://othersource.com/rss');
	$has_something = preg_match('/title>(.+?)<\/title/', $rss);
	if ( $has_something ) $updates[] = time();
}

Теперь переменная $updates будет расти до максимального предела. Когда будет достигнут лимит по памяти, скрипт будет остановлен. Уследить за всеми переменными довольно тяжело, поэтому лучше использовать функции. Все переменные, созданные внутри функции будут удаляться после ее завершения:

<?
while ( true ) process();

function process()
{
	$rss = file_get_contents('http://somesite.com/rss');
	$has_something = preg_match('/title>(.+?)<\/title/', $rss);
	if ( $has_something ) $updates[] = time();

	$rss = file_get_contents('http://othersource.com/rss');
	$has_something = preg_match('/title>(.+?)<\/title/', $rss);
	if ( $has_something ) $updates[] = time();
}

Самое важное

  • Обязательно используйте opCache для PHP. Это экономит существенную долю ресурсов.
  • Используйте FastCGI (лучше всего Nginx + PHP-fpm).
  • Функции в крон задачах помогут избежать утечек памяти.
  • Кэширование медленных участков кода часто самое простое и эффективное решение.
  • Помните о важных мелочах.

Подпишитесь на Хайлоад с помощью Google аккаунта
или закройте эту хрень