Существует ряд правил, которые стоит соблюдать, чтобы увеличить скорость работы приложений на 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
  • тяжелые выборки из баз данных
  • обработка больших файлов
session.save_handler = memcache session.save_path = "tcp://localhost:11211 "

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

Кроме этого, такая схема хранения позволит масштабироваться на несколько серверов.

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

ООП

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

set_title($_GET["title"]); $post->set_description($_GET["description"]); $post->save();

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

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

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

$_GET["title"], "description" => $_GET["description"]]);

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

"; }

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

Мелочи

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

include "/var/www/file.php"; file_get_contents("/var/www/dir/data.txt");

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

class posts { const PER_PAGE = 10; ... }

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

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

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

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

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

strpos($post["title"], "хорошо");

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

$post["title"] = "В этом случае нет дополнительной обработки переменных"

PHP cron-скрипты

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

(.+?)<\/title/", $rss, $matches ); }

(.+?)<\/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 будет расти до максимального предела. Когда будет достигнут лимит по памяти, скрипт будет остановлен. Уследить за всеми переменными довольно тяжело, поэтому лучше использовать функции. Все переменные, созданные внутри функции будут удаляться после ее завершения:

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).
  • Функции в крон задачах помогут избежать утечек памяти.
  • Кэширование медленных участков кода часто самое простое решение для ускорения работы.
  • Помните о

В наши дни, когда выделенный канал Интернета стал нормой, не особо стоит переживать о размере страничек. Однако, все таки на это стоит обращать внимание. Если Вы хотите уменьшить нагрузку на сервер, сократить количество HTTP запросов - для этого существует несколько техник. Этот урок расскажет о нескольких PHP трюках (кеширования, сжатие).

1. Объединение CSS файлов с помощью PHP .

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

Подготовка

Перед оптимизацией CSS файлов, нам необходимы стили для работы. Давайте создадим несколько файлов с стилями:

// main.css
// CSS для примера

body {
width: 800px;
margin: 0 auto;
color: grey;
}

#wrapper {
margin-top: 30px;
background: url(../images/cats.png);
}
// typography.css
// CSS для примера

body {
font-family: Arial, san-serif;
font-weight: bold;
}

strong {
font-size: 120%;
}
// forms.css
// CSS для примера

form {
position: relative;
top: 400px;
z-index: 99;
}

input {
height: 50px;
width: 400px;
}

Нам необходимо извлечь содержание всех файлов и соединить в определенном порядке. Значит наш скрипт должен получить имена таблиц стилей через УРЛ параметры, открыть эти файлы и соединить.

//Определяем переменные
$cssPath = "./css/";
if (isset($_GET["q"])) {
$files = $_GET["q"];
// Получаем массив файлов


foreach ($files as $key => $file) {
}

$cssData = "";
foreach ($files as $file) {

fclose($fileHandle);
}
}
// Скажи браузеру, что у нас CSS файл
if (isset($cssData)) {
echo $cssData;
} else {
}
?>

//Определяем переменные
// --- NOTE: PATHS NEED TRAILING SLASH ---
$cssPath = "./css/";
if (isset($_GET["q"])) {
$files = $_GET["q"];
// Got the array of files!

//Давайте убедимся, что в файловых именах нет страшных символов:) .
foreach ($files as $key => $file) {
$files[$key] = str_replace(array("/", "\\", "."), "", $file);
}

Этот код устанавливает путь к папке со стилями и проверяет на наличие файлов. Путь к папке должен иметь слеши вначале и в конце названия папки, так как в противном случае у нас будет масса ошибок. Далее мы проверяем каждое имя файла и удаляем точки и/или слеши.

$cssData = "";
foreach ($files as $file) {
$cssFileName = $cssPath . $file . ".css";
$fileHandle = fopen($cssFileName, "r");

$cssData .= "\n" . fread($fileHandle, filesize($cssFileName));
fclose($fileHandle);
}
}

Теперь нам необходимо создать общую таблицу стилей из файлов. Для этого мы запускаем цикл, который просматривает массив файлов, открывает каждый файл и соединяет в единый файл. "\n" добавляет новую строку для порядка и чистоты. Функция filesize() используется для того, чтобы узнать длину файла и передать fread().

// Скажи браузеру, что у нас CSS файл
header("Content-type: text/css");
if (isset($cssData)) {
echo $cssData;
echo "\n\n// Generated: " . date("r");
} else {
echo "// Files not avalable or no files specified.";
}
?>

Последняя часть кода передает все стили браузеру. Это значит, что нам необходимо сказать PHP, что мы передаем CSS информацию и что PHP должно проинформировать об этом браузер. Мы делаем это с помощью функции header(), и устанавливаем Content-type: text/css. Далее мы передаем CSS клиенту. Но перед этим проверяем наличие CSS стилей в файле. Если их нет, значит это означает, что названия CSS файлов не были переданы. Если же у нас есть файлы мы их передаем и добавляем сообщение о генерации.

Проверка

Теперь время проверить скрипт. Создаем папку и файлы в ней. Взгляните на структуру папки ниже. Если Вам необходимо другая структура, тогда не забудьте поменять пути.

Теперь загрузите все файлы в корень сайта и обратитесь к файлу index.php через браузер. Вас должна встретить фраза "Files not available or no files specified" (файлы не доступны или не указаны). Это означет, что мы не дали названия файлов, которые необходимо соединить. Однако, хорошая новость - что все работает без ошибок. Давайте попробуем напечатать в браузере "index.php?q=main". У Вас на экране появится содержание файла main.css. Если нам необходимо извлечь несколько файлов и объединить их, нам необходимо отправить следющий запрос "index.php?q=main&q=forms". Как Вы видите мы можем повторять "q=" сколько угодно раз. Вы можете объединить в один файл хоть 50 таблиц стилей.

Заключение

Данный метод может быть очень полезным, и иметь множество плюсов. У Вас может быть общая таблица стилей для всего сайта, и отдельная, например, для страниц с формами.

Небольшое предупреждение: если Вы поместите файл index.php в любую папку (не в папку с CSS), тогда Вам необходимо прописывать относительные пути к фоновым изображениям так, как будто index.php является Вашей таблицей стилей. Так будет думать браузер.

2. Удаление пустых строк из HTML и CSS

Многие из нас используют большое количество пустых строк при написании кода. Хорошие новости - пустые строки в PHP не передаются браузеру. Однако, передаются в HTML.

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

Подготовка

Ниже представлены коды для HTML и CSS файлов.

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">


Hey a Page!






Lorem Ipsum dol...



body {
min-height: 800px;
background: black;
font-size: 18px;
}
#wrapper {
width: 960px;
margin: 20px auto;
padding: 15px;
}
#header h1 {
text-indent: -99999em;
background: url(../images/header.png);

Display: block;
width: 100%;
height: 48px;
}
#mainbody {
font-weight: bold;
}

Преимуществом данного скрипта является то, что он может одновременно работать как с HTML, так и с CSS. Скрипт загружает файл, убирает все пустые строки, оставляет только 1 пробел, чтобы слова не соединялись в одно целое.

$fileDirectory = "";
$file = $_GET["q"];
$ext = $nameExplode;

//Проверка для хакеров
die("Hackers...!");
} else {
//Начнем


//Чудеса регулярных выражений

fclose($handle);
//Выводим данные
if ($ext == "css") {
header("Content-type: text/css");
}
echo $newData;
}
?>

Более подробно о каждой части кода

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

$fileDirectory = "";
$file = $_GET["q"];
$nameExplode = explode(".", $file);
$ext = $nameExplode;
$fileName = $fileDirectory . $file;

Этот код задает необходимые переменные. Снова, мы передаем все данные через "q". Тут также определяется папка для файлов.

If ($ext != "css" AND $ext != "htm" AND $ext != "html") {
//Проверка для хакеров
die("Hackers...!");
} else {

Тут мы проверяем действительно ли файл является CSS или HTML.

//Начнем
$handle = fopen($fileName, "r");
$fileData = fread($handle, filesize($fileName));
//Чудеса регулярных выражений
$newData = preg_replace("/\s+/", " ", $fileData);
fclose($handle);
//Выводим данные
if ($ext == "css") {

header("Content-type: text/css");
}
echo $newData;
}
?>

Этот код открывает и читает файл - и потом убирает по возможности все пустое пространство. Это достигается путем использования регулярных выражений. Находятся все пробелы, табуляции, пустые строки и заменяются на пробел.

Работает ли это?

Если Вы в браузере напечатаете "index.php?q=css.css", то увидите одну линию CSS. Значит все работает! Если откроете исходный код страницы, то увидите такую же картину. С помощью данного метода мы уменьшили 314 символьный CSS файл до 277 символов. HTML файл с 528 до 448 символов. Неплохо для 15 строк кода.

Заключение

Это отличный пример того, как мы можем сделать много используя несколько строк кода. Если Вы взгляните на исходный код таких сайтов как Гугл, то заметите, что там практически нет пустых строк.

3. Кеширование PHP скриптов.

Я покажу Вам как настроить кеширование Ваших скриптов используя пример выше. Цель - ускорить работу сайта. Суть очень проста - данные не будут генерироваться каждый раз при обращении к сайту. Они будут храниться в кеше.

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

Подробнее

$fileDirectory = "";
$file = $_GET["q"];
$nameExplode = explode(".", $file);
$ext = $nameExplode;
$fileName = $fileDirectory . $file;
//-- WE HAVE ENOUGH DATA TO GENERATE A CACHE FILE NAME HERE --
if ($ext != "css" AND $ext != "htm" AND $ext != "html") {
//Check for evil people...
die("Hackers...!");
} else {

//-- WE CAN INTERCEPT AND CHECH FOR THE CACHED VERSION HERE --

//Lets get down to business
$handle = fopen($fileName, "r");
$fileData = fread($handle, filesize($fileName));
//Now for some regex wizardry!
$newData = preg_replace("/\s+/", " ", $fileData);

Fclose($handle);
//Time to output the data.

//-- NOW WE CAN STORE THE NEW DATA IF REQUIRED AND OUTPUT THE DATA --

If ($ext == "css") {
header("Content-type: text/css");
}
echo $newData;
}
?>

$fileDirectory = "";
$file = $_GET["q"];
$nameExplode = explode(".", $file);
$ext = $nameExplode;
$fileName = $fileDirectory . $file;
$cacheName = "./cache/" . $nameExplode . $nameExplode . ".tmp";
if ($ext != "css" AND $ext != "htm" AND $ext != "html") {
//Хакеры
print_r($ext);
die("Hackers...!");
} else {
if (file_exists($cacheName) AND filemtime($cacheName) > (time() - 86400)) {


fclose($cacheHandle);
$isCached = TRUE;
} else {
//Начнем
$handle = fopen($fileName, "r");
$fileData = fread($handle, filesize($fileName));
//Чудеса регулярных выражений
$newData = preg_replace("/\s+/", " ", $fileData);
fclose($handle);
//Кешируем


fclose($cacheHandle);
$isCached = FALSE;
}
//Выводим данные
if ($ext == "css") {
header("Content-type: text/css");
if ($isCached) {

}
} else {
if ($isCached) {
echo "";

}
}
echo $newData;

Объяснение

В данном скрипте была добавлена функция обновления кеша каждые 24 часа. Это удобно. Допустим, если Вы поменяли что-либо на сайте - можете подождать 24 часа или же очистить кеш.

$cacheName = "./cache/" . $nameExplode . $nameExplode . ".tmp";

Этот отрезок кода достает название файлов и их расширения, склеивает их вместе и добавляет в кеш с правильным расширением ".tmp".

If (file_exists($cacheName) AND filemtime($cacheName) > (time() - 86400)) {
$cacheHandle = fopen($cacheName, "r");
$newData = fread($cacheHandle, filesize($cacheName));
fclose($cacheHandle);
$isCached = TRUE;
} else {

Тут мы проверяем наличие сохраненного кеша и был ли он создан в течении последних 24 часов (значение в секундах - можно поменять на любое другое). Если оба условия выполняются, открываем файл и извлекаем содержание, чтобы заменить им результат работы скрипта. Мы также устанавливаем $isCached true для вывода дополнительных сообщений в конце.

//Lets cache!
$cacheHandle = fopen($cacheName, "w+");
fwrite($cacheHandle, $newData);
fclose($cacheHandle);
$isCache = FALSE;
}

Кешируем результат скрипта для использования при дальнейших обращениях. Мы просто открываем файл в режиме записи, сбрасываем туда информацию и закрываем.

//Time to output the data.
if ($ext == "css") {
header("Content-type: text/css");
if ($isCached) {
echo "// Retrieved from cache file. \n";
}
} else {
if ($isCached) {
echo "";
}
}

Эта часть скрипта была немного модифицирована, чтобы мы могли увидеть результат работы. Если содержание файла было извлечено из кеша, мы можем добавить сообщение об этом.

Пробуем

Если мы воспользуемся скриптом вновь, мы не увидим изменений до тех пор, пока не обновим страницу. Снизу увидим надпись, что файл взят из кеша.

Заключение

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

Подводим Итоги

В данном уроке я Вам показал несколько удобных и простых путей ускорения Вашего сайта с помощью PHP.


Как без особых усилий заставить PHP -код работать на порядок быстрее ? Перед тем как задаваться вопросами кеширования и масштабирования стоит попробовать оптимизировать код. Есть ряд несложных правил:

Еще про оптимизацию....

При вставке кусков PHP-кода в HTML страницы всегда используйте полные открывающие и закрывающие скобки ! Это обезопасит Вас от вариаций настройки php.ini short_open_tag на разных серверах и возможно сэкономит много времени при переносе или загрузке проектов на разные сервера.

Старайтесь использовать функцию вывода echo вместо printf и sprintf там где возможно. Нет надобности использовать эти функции, так как они выполняются медленней потому, что созданы для интерпретации и вывода строки с ее обработкой, подстановкой значений, в отформатированном виде. О чем и говорит буква f в конце названия этих 2-х функций.

Sprintf("мама"); printf("папа");

Echo "мама"; echo "папа";

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

Лучший вариант(самый быстрый)

Echo "Вес равен: ".$weight;

Худший вариант(медленный):

Echo "Вес равен: $weight";

Если Вам нужно проверить не равно ли возвращенное значение функции нулю (а функция сама по себе возвращает только положительные или только отрицательные значения), то лучше использовать оператор сравнения. Он выполняется быстрей, нежели конкретное сравнение значений.

$i = 0; if ($i != 0) { //Не равно } else { //Равно }

$i = 0; if ($i > 0) { //Не равно } else { //Равно } Нужно также учитывать, что если строка принимает только пустое значения, либо пользовательские строковые данные, то вместо сравнения строки со строкой, для выявления ее пустоты, так же можно использовать сравнение с нулем, которые выполнится быстрее.

Для проверки строки на пустоту используйте функцию trim($str) ; Она не только проверит заполнена ли строка, но также обрежет несущественные символы - пробелы (табуляции, white-spaces) и вернет положительное значение, в случае если в строке ей действительно какие то значимые символы.

If ($str != "") { //обработка строки }

If (trim($str)) { //обработка строки }

Для получения данных из форм методом Get и Post лучше использовать следующий минимальный набор самописных функций:

GetParam ($array, $value, $default = "") { return (isset($array[$value])) ? $array[$value] : $default; } GetParamSafe ($array, $value, $default = "") { return (isset($array[$value])) ? addslashes($array[$value]) : $default; }

Функция GetParam($_POST, "myvar", "empty") к примеру коректно получит данные из $_POST["myvar"], и в случае если $_POST переменная не существует вернет значение по умолчанию, без всяких Waring и Notice. Фунция GetParamSafe($_POST, "myvar", "empty") делает ту же операцию, только возвращает экранированную переменную. Для защиты от SQL инъекций к примеру. А данная конструкция позволяет получить целочисленное число из $_POST.

Intval(GetParam($_POST, "myvar", "empty")):

В случае если в массиве $_POST лежало совсем не число функия вернет 0;

Для простого сравнения строк не используйте preg_match() или preg_match_all() . Используйте strstr() и strpos() .

При получении строк из базы данных (MySQL к примеру) старайтесь использовать функцию mysql_fetch_object . К примеру при изменении кода запроса с

$query = "SELECT field7, field3 FROM mytable WHERE id = 5" на $query = "SELECT * FROM mytable WHERE id = 5" код вывода строки полученной из этих запросов $row = mysql_fetch_array(mysql_query($query)); echo $row."-->".$row; //перестанет работать, в то время, как $row = mysql_fetch_object(mysql_query($query)); echo $row->field7."-->".$row->field3; // останется работоспособным.

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

При формировании больших запросов вставки данных в БД через insert все строчки старайтесь поместить в один-три insert"а. Выполнение каждой строчки отдельно не только загрузит сервер БД, но и задержит работу Вашего скрипта.

В случае если необходимо в разных местах (разных классах) одной системы использовать одни и те же сложно вычисляемые данные (например которые достаются из БД через запрос с последющей обработкой строк), старайтесь их вычислять единожды, хранить глобально для всей системы и передавать в класс(скрипт) один раз, непосредственно при создании класса (подключении скрипта)

При больших нагрузках на Web-сервер задумайтесь над использованием стандартных решений для включения кэша(кэш-технологии). Например бесплатный PHP класс JCache_Lite_Function.

При проектировании/разработке больших систем отдавайте предпочтение Объектно-Ориентированному программированию с использование шаблонов проектирования. Наиболее частые шаблоны: MVC, PageController, BodyHandler, Fabric...


Читать дальше:

Тема данной статьи - тот факт, что применение FastCGI в PHP не ускоряет время загрузки PHP-кода по сравнению, например, с mod_php.

Большинство традиционных языков Web-программирования (Perl, Ruby, Python и т. д.) поддерживают написание скриптов, работающих в так называемом «FastCGI-режиме». Более того, Ruby on Rails, к примеру, вообще невозможно использовать в CGI-режиме, т.к. он тратит на подключение всех своих многочисленных библиотек десятки секунд.

Ниже я расскажу о методе, который позволяет в ряде случаев ускорить загрузку объемного PHP-кода более чем в 20 раз, не получая при этом побочных эффектов и значительных неудобств. Но вначале давайте поговорим об основах…

Что такое FastCGI?

Вначале поговорим, что называется, о «классическом» FastCGI, который применяют в Си, Perl, Ruby и т. д. В PHP же FastCGI имеет свои особенности, мы их рассмотрим чуть позже. Сейчас речь идет о не-PHP.

FastCGI работает следующим образом: скрипт загружается в память, запускает некоторый тяжелый код инициализации (например, подключает объемистые библиотеки), а затем входит в цикл обработки входящих HTTP-запросов. Один и тот же процесс-скрипт обрабатывает несколько различных запросов один за другим, что отличается от работы в CGI-режиме, когда на каждый запрос создается отдельный процесс, «умирающий» после окончания обработки. Обычно, обработав N запросов, FastCGI-скрипт завершается, чтобы сервер перезапустил его вновь уже в «чистой песочнице».

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

Старые скрипты, написанные с расчетом на CGI, приходится дорабатывать, чтобы они могли работать в FastCGI-окружении (касается использования FastCGI в Си, Perl и т. д.; на PHP дорабатывать не нужно, но у этого свои недостатки, см. ниже). Действительно, ведь раньше скрипт стартовал каждый раз «с чистого листа», а теперь ему приходится иметь дело с тем «мусором», который остался от предыдущего запроса. Если раньше CGI-скрипт на Perl выглядел как

use SomeHeavyLibrary; print "Hello, world!\n";

то после переделки под FastCGI он становится похож на что-то типа

use SomeHeavyLibrary; while ($request = get_fastcgi_request()) { print "Hello, world!\n"; }

Таким образом, получается экономия в скорости ответа: ведь каждый раз, когда приходит запрос, скрипту не нужно выполнять «тяжелый» код инициализации библиотек (подчас занимающий десятки секунд).

FastCGI имеет значительный недостаток: новый запрос начинает обрабатываться не в «чистом» окружении, а в том, которое осталось «с прошлого раза». Если в скрипте имеются утечки памяти, они постепенно накапливаются до тех пор, пока не произойдет крах. Это же касается и ресурсов, которые забыли освободить (открытые файлы, соединения с БД и т. д.). Есть и еще один недостаток: если код скрипта изменился, то приходится как-то сообщать об этом FastCGI-серверу, чтобы он «убил» все свои процессы и начал заново.

Собственно, техника «иницилизируйся один раз, обрабатывай много запросов» работает не только в FastCGI. Любой сервер, написанный на том же самом языке, что и скрипт, под ним запущенный, использует ее неявно. Например, сервер Mongrel написан целиком на Ruby как раз для того, чтобы под ним быстро запускать Ruby On Rails. Сервер Apache Tomcat, написанный на Java, быстро выполняет Java-сервлеты, т.к. не требует их повторной инициализации. Технология mod_perl также основана на том, что Perl-код не выгружается между запросами, а остается в памяти. Конечно, все эти серверы имеют те же самые проблемы с непредсказуемостью, утечками памяти и сложностью перезапуска, что и FastCGI-приложение.

Почему FastCGI не ускоряет PHP

Вероятно, вы слышали, что PHP тоже можно запускать в режиме FastCGI, и что так делают многие нагруженные проекты (Мамба, некоторые проекты Mail.Ru и т. д.). Это якобы дает «существенный прирост» производительности, потому что (согласно слухам) FastCGI экономит время инициализации скрипта и подключения библиотек.

Не верьте! В действительности поддержка FastCGI в PHP имеет чисто номинальный характер. Точнее, она не дает преимуществ в том смысле, в котором ей привыкли оперировать для уменьшения времени инициализации скрипта. Конечно, вы можете запустить PHP FastCGI-сервер и даже заставить nginx или lighttpd работать с ним напрямую, однако прирост скорости на загрузку кода, который вы от этого получите, будет нулевым. Тяжелые PHP-библиотеки (например, Zend Framework) как загружались долго в mod_php- или CGI-режимах, так и будут продолжать загружаться долго в режиме FastCGI.

Собственно, это неудивительно: ведь чтобы запустить любой PHP-скрипт в FastCGI-режиме, его не приходится дорабатывать. Ни строчки измененного кода! Когда я впервые решил поэкспериментировать с FastCGI в PHP, я потратил несколько часов времени на поиски в Интернете информации о том, как именно следует модифицировать PHP-код, чтобы оптимально запускать его в режиме FastCGI. Я проштудировал всю документацию PHP и несколько десятков форумов PHP-разработчиков, даже просмотрел исходный код PHP, но так и не нашел ни единой рекомендации. Имея прежний опыт работы с FastCGI в Perl и Си, я был несказанно удивлен. Однако все встало на свои места, когда выяснилось, что изменять код не нужно и, хотя в рамках одного FastCGI-процесса обрабатываются несколько соединений, PHP-интерпретатор каждый раз инициализируется заново (в отличие от «классического» FastCGI). Более того, похоже, большинство PHP-разработчиков, радостно использующих FastCGI+PHP, даже не подозревают о том, что оно должно работать как-то по-другому…

eAccelerator: ускорение повторной загрузки PHP-кода

Каждый раз, когда PHP-скрипт получает управление, PHP компилирует (точнее, транслирует) код скрипта во внутреннее представление (байт-код). Если файл небольшой, трансляция происходит очень быстро (Zend Engine в PHP - один из лидеров по скорости трансляции), однако, если включаемые библиотеки «весят» несколько мегабайтов, трансляция затягивается.

Существует ряд инструментов, кэширующих в разделяемой оперативной памяти (shared memory) оттранслированное внутреннее представление PHP-кода. Таким образом, при повторном включении PHP-файла он уже не транслируется, а байт-код берется из кэша в памяти. Естественно, это значительно ускоряет работу.

Одним из таких инструментов является eAccelerator. Он устанавливается в виде расширения PHP (подключается в php.ini) и требует самой минимальной настройки. Рекомендую включить в нем режим кэширования байт-кода исключительно в оперативной памяти и отключить сжатие (установить параметры eaccelerator.shm_only=1 и eaccelerator.compress=0). Также установите и настройте контрольную панель control.php, идущую в дистрибутиве eAccelerator, чтобы в реальном времени отслеживать состояние кэша. Без контрольной панели вам будет очень трудно проводить диагностику, если eAccelerator по каким-то причинам не заработает.

Преимущество eAccelerator в том, что он работает весьма стабильно и быстро даже на больших объемах PHP-кода. У меня ни разу не возникало проблем с этим инструментом (в отличие от Zend Accelerator, к примеру).

«Мой скрипт вместе с библиотеками занимает 5 МБ, как же быть?..»

Думаете, 5 МБ кода - это чересчур много для PHP? Ничего подобного. Попробуйте воспользоваться такими системами, как Zend Framework и Propel , чтобы убедиться в обратном. Zend Framework целиком занимает как раз около 5 МБ. Классы, сгенерированные Propel-ом, также весьма объемисты и могут отнять еще несколько мегабайтов.

Многие на этом месте посмеются и скажут, что не надо использовать Zend Framework и Propel, т.к. они «тормозные». Но действительность заключается в том, что тормозные вовсе даже не они… Плохую производительность имеет метод, который по умолчанию использует PHP для загрузки кода. К счастью, ситуацию нетрудно исправить.

Чтобы не быть голословным, я приведу результаты небольшого тестирования, которое я специально провел в «чистом» окружении, не привязанном к какому-либо конкретному проекту. Тестировалась скорость подключения всех файлов Zend Framework (за исключением Zend_Search_Lucene). Предварительно из кода были вырезаны все вызовы require_once, а загрузка зависимостей поизводилась только через механизм autoload .

Итак, всего подключались 790 PHP-файлов общим объемом 4.9 МБ. Немало, верно? Подключение осуществлялось примерно так:

function __autoload($className) { $fname = str_replace("_", "/", $className) . ".php"; $result = require_once($fname); return $result; } // Подключаем классы один за другим в порядке их зависимостей. class_exists("Zend_Acl_Assert_Interface"); class_exists("Zend_Acl_Exception"); class_exists("Zend_Acl_Resource_Interface"); class_exists("Zend_Acl_Resource"); // ... и так для всех 790 файлов

Благодаря тому, что используется autoload, вызов class_exists() заставляет PHP подключить файл соответствующего класса. (Это самый простой способ «дернуть» autoload-функцию.) Порядок подключения я выбрал таким, чтобы каждый следующий класс уже имел загруженными все свои зависимые классы на момент запуска. (Этот порядок нетрудно установить, просто печатая в браузер значение $fname в функции __autoload).

Вот результаты тестирования с eAccelerator-ом и без на моем не очень мощном ноутбуке (Apache, mod_php):

  • Подключение всех файлов по одному, eAccelerator выключен: 911 мс.
  • Подключение всех файлов по одному, eAccelerator включен: 435 мс. Занято 15 М кэш-памяти под байт-код.

Как видите, eAccelerator дает примерно двукратное ускорение на 790 файлах общим объемом 4.9 МБ. Слабовато. К тому же, 435 мс - явно чересчур для скрипта, который только и делает, что подключает библиотеки.

А теперь добавим стероидов

Ходят слухи, что PHP гораздо быстрее загружает один большой файл, чем десять маленьких того же суммарного объема. Я решил проверить это утверждение, объединив весь Zend Framework в один файл размером 4.9 МБ и подключив его единственным вызовом require_once. Давайте посмотрим, что получилось.

  • Включение одного большого слитого файла, eAccelerator выключен: 458 мс.
  • Включение одного большого слитого файла, eAccelerator включен: 42 мс. Занято 31 МБ кэш-памяти под байт-код.

Первая строчка говорит о том, что один большой файл размером 4.9 МБ и правда загружается быстрее, чем 790 маленьких: 458 мс против 911 мс (см. выше). Экономия в 2 раза.

А вот вторая строчка заставила меня от удивления подпрыгнуть на стуле и перепроверить результат несколько раз. Надеюсь, это же произойдет и с вами. Действительно, 42 мс - это в 11 раз быстрее, чем с отключенным eAccelerator-ом! Получается, что eAccelerator еще меньше любит мелкие файлы (кстати, даже в режиме eaccelerator.check_mtime=0): экономия в 11 раз.

Итак, мы действительно получили ускорение загрузки в 22 раза, как и было обещано в заголовке. Раньше весь Zend Framework, разбитый на файлы, подключался 911 мс, а с использованием eAccelerator и объединенем всех файлов в один - 42 мс. И это, заметьте, не на реальном сервере, а всего лишь на рядовом ноутбуке.

Вывод: ускорение в 22 раза

Подведем итоги.

  • Слияние всех файлов в один большой плюс включение eAccelerator для этого большого файла дает ускорение в 22 раза при объеме кода 4.9 МБ и числе файлов 790.
  • В случае небольшого числа файлов значительного объема eAccelerator может дать 10-кратное ускорение. Если файлов много, а суммарный объем большой, то ускорение примерно в 2 раза.
  • Расход кэш-памяти зависит от числа файлов разбиения: при фиксированном объеме чем файлов больше, тем расход меньше.

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

Заметьте также, что мы подключали весь Zend Framework. В реальных скриптах объем кода будет сильно меньше, т.к. обычно для работы требуется лишь незначительная часть ZF. Но даже при условии, что библиотеки занимают 4.9 МБ, мы получаем время загрузки 42 мс - вполне приемлемое для PHP-скрипта. Ведь в нагруженных проектах PHP-скрипты могут работать и несколько сотен миллисекунд (Facebook, Мой Круг и т. д.).

Конечно, если вы планируете запускать FastCGI в PHP не из соображений производительности, а просто чтобы не «завязываться» за Apache и ограничиться связкой «nginx+PHP» или «lighttpd+PHP», ничто этому не мешает. Более того, задействовав eAccelerator для FastCGI+PHP и слив много файлов кода в один большой, вы получите то же самое ускорение, которое я описал выше. Однако не тешьте себя надеждами, что ускорение дал именно FastCGI: это не так. Применяя mod_php+eAccelerator, вы достигли бы практически такого же результата, что и FastCGI+eAccelerator.

Вручную объединять все файлы библиотек в один - утомительное занятие. Лучше написать утилиту, которая будет автоматически анализировать список PHP-файлов, подключенных скриптом, а при следующем запуске - объединять эти файлы и записывать во временную директорию (если это еще не сделано), после чего - подключать по require_once. Сегодня я оставляю написание такой утилиты (плюс-минус детали) на совести читателя.

Также рекомендую вам отказаться от явного включения файлов по require_once и переходить на autoload. Только не пытайтесь использовать для этого Zend_Loader: он очень медленный (по моим замерам, подключение ста файлов отнимет дополнительно около 50 мс). Лучше напишите собственную несложную autoload-функцию, которая будет быстро выполнять всю работу. Autoload позволит вам безопасно объединять несколько файлов библиотек в один, не думая о том, как бороться с «ручными» require_once.

Наконец, применяйте функцию set_include_path() , чтобы код подключения библиотек выглядел вот так:

require_once "Some/Library.php";

require_once LIB_DIR . "/Some/Library.php";

Константы, определяющие путь к директории библиотек явным образом, - большое зло и усложнение программы. Они также косвенно противоречат стандартам кодирования Zend Framework и PEAR, которых я тоже рекомендую по возможности придерживаться.

Итак, хотите использовать «тяжелые» библиотеки в PHP-скриптах - на здоровье! PHP - скриптовый язык, по-настоящему позволяющий это делать без оглядки на неудобства FastCGI и проблемы «встроенных» серверов.

Всем доброго времени суток.

  1. Если метод может быть статическим, объявляйте его статическим.
  2. echo быстрее, чем print.
  3. Передавайте в echo несколько параметров, вместо того, чтобы использовать конкатенацию строк.
  4. Устанавливайте максимальное количество проходов ваших циклов for до цикла, а не во время его выполнения.
  5. Удаляйте свои переменные для освобождения памяти, тем более, если это большие массивы.
  6. Остерегайтесь магических методов, таких как __set, __get, __autoload.
  7. require_once дорого обходится.
  8. Указывайте полные пути в конструкциях include/require, меньше времени будет тратится на поиск файла.
  9. Если вам необходимо определить время, когда скрипт был запущен, используйте $_SERVER[’REQUEST_TIME’] вместо time().
  10. Старайтесь использовать strncasecmp, strpbrk и stripos вместо регулярных выражений.
  11. str_replace быстрее, чем preg_replace, но strtr быстрее, чем str_replace.
  12. Если функция, как и функции замены строк, может принимать в аргументы как массивы, так и одиночные символы, и если ваш список аргументов не слишком длинный, подумайте над тем, чтобы записать несколько одинаковых выражений замены, проходя один символ за раз, вместо одной строки кода, которая принимает массив как аргумент поиска и замены
  13. Лучше выбирать утверждения при помощи конструкции else if, чем использовать несколько конструкций if.
  14. Подавление ошибок при использовании @ работает очень медленно.
  15. Используйте модуль Apache mod_deflate.
  16. Закрывайте свои соединения с БД, когда закончите работать с ними.
  17. $row["id"] в семь раз быстрее, чем $row.
  18. Сообщения об ошибках дорого стоят
  19. Не используйте функции внутри условия цикла for, например как здесь: for ($x=0; $x < count($array); $x). В данном случае функция count() будет вызываться с каждым проходом цикла.
  20. Инкремент локальной переменной в методе - самый быстрый. Почти также работает инкремент локальной переменной в функции.
  21. Инкремент глобальной переменной в два раза медленее, чем локальной.
  22. Инкремент свойства объекта (т.е. $this->prop++) в три раза медленнее, чем локальной переменной.
  23. Инкремент неопределённой переменной в 9-10 раз медленнее, чем заранее инициализированной.
  24. Объявление глобальной переменной, без использования её в функции, также замедляет работу (примерно на ту же величину, что и инкремент локальной переменной). Вероятно, PHP осуществляет проверку на существование переменной.
  25. Скорость вызов метода, судя по всему, не зависит от количества методов, определённых в классе. Я добавил 10 методов в тестовый класс (до и после тестового метода), без изменения производительности.
  26. Методы в производных классах работают быстрее, чем они же, определённые в базовом классе.
  27. Вызов функции с одним параметром и пустым телом функции в среднем равняется 7-8 инкрементам локальной переменной ($localvar++). Вызов похожего метода, разумеется, около 15 инкрементов.
  28. Ваши строки, определённые при помощи ", а не ", будут интерпретироваться чуть быстрее, т.к. PHP ищет переменные внутри "..", но не "...". Конечно, вы можете использовать это только тогда, когда в вашей строке нет переменных.
  29. Строки, разделённые запятыми, выводятся быстрее, чем строки, разделённые точкой. Примечание: это работает только с функцией echo, которая может принимать несколько строк в качестве аргументов.
  30. PHP-скрипты будут обрабатываться, как минимум, в 2-10 раз медленнее, чем статические HTML-страницы. Попробуйте использовать больше статических HTML-страниц и меньше скриптов.
  31. Ваши PHP-скрипты перекомпилируются каждый раз, если скрипты не кэшируются. Кэширование скриптов обычно увеличивает производительность на 25-100% за счёт удаления времени на компиляцию.
  32. Кэшируйте, насколько это возможно. Используйте memcached — это высокопроизводительная система кэширования объектов в памяти, которая повышает скорость динамических веб-приложений за счёт облегчения загрузки БД. Кэшированный микрокод полезен тем, что позволяет вашему скрипту не компилироваться снова для каждого запроса.
  33. При работе со строками, когда вам понадобится убедиться в том, что строка определённой длины, вы, разумеется, захотите использовать функцию strlen(). Эта функция работает очень быстро, ведь она не выполняет каких-либо вычислений, а лишь возвращает уже известную длину строки, доступную в zval-структуре (внутренняя структура C, используемая при работе с переменными в PHP). Однако потому, что strlen() — функция, она будет работать медленно за счёт вызова некоторых операций, таких как приведение строки в нижний регистр и поиска в хэш-таблице, только после которых будут выполнены основные действия функции. В некоторых случаях вы сможете ускорить свой код за счёт использования хитрости с isset().
    Было: if (strlen($foo) < 5) { echo «Foo is too short»; }
    Стало: if (!isset($foo{5})) { echo «Foo is too short»; }
    Вызов isset() быстрее, чем strlen() потому, что, в отличие от strlen(), isset() - не функция, а языковая конструкция. За счёт этого isset() не имеет практически никаких накладных расходов на определение длины строки.
  34. Инкремент или декремент переменной при помощи $i++ происходит немного медленнее, чем ++$i. Это особая специфика PHP, и не нужно таким образом модифицировать свой C и Java-код думая, что он будет работать быстрее, этого не произойдёт. ++$i будет быстрее в PHP потому, что вместо четырёх команд, как в случае с $i++, вам понадобится только три. Пост-инкремент обычно используется при создании временных переменных, которые затем увеличиваются. В то время, как пре-инкремент увеличивает значение оригинальной переменной. Это один из способов оптимизации PHP-кода в байт-код утилитой Zend Optimizer. Тем не менее, это хорошая идея, поскольку не все байткод-оптимизаторы оптимизируют это, также остаётся немало скриптов, работающих без оптимизации в байткод.
  35. Не всё должно быть ООП, часто это излишне, поскольку каждый метод и объект занимает много памяти.
  36. Не определяйте каждую структуру данных как класс, массивы бывают очень полезны
  37. Не слишком разбивайте методы. Думайте, что вы действительно будете повторно использовать.
  38. Вы всегда можете разбить код на методы позже, по необходимости.
  39. Используйте бесчисленное количество предопределённых функций.
  40. Если в вашем коде есть функции, выполняющиеся очень долго, обдумайте их написание на C в виде расширения
  41. Профилируйте свой код. Профилирование покажет вам, как много времени выполняются части вашего кода.
  42. mod_gzip — модуль Apache, который позволяет сжимать ваши данные на лету и может уменьшить объем передаваемых данных до 80%.