![]() |
![]() |
|
||
![]() |
|
|||||||||||||||||||||||||||||||||||||||||||||||||||
| [9 мая 2008 г.] |
|
Тема данной |
Большинство традиционных языков Web-программирования (Perl, Ruby, Python и т. д.) поддерживают написание скриптов, работающих в так называемом "FastCGI-режиме". Более того, Ruby on Rails, к примеру, вообще невозможно использовать в CGI-режиме, т.к. он тратит на подключение всех своих многочисленных библиотек десятки секунд.
Ниже я расскажу о методе, который позволяет в ряде случаев ускорить загрузку объемного PHP-кода более чем в 20 раз, не получая при этом побочных эффектов и значительных неудобств. Но вначале давайте поговорим об основах...
Вначале поговорим, что называется, о "классическом" FastCGI, который применяют в Си, Perl, Ruby и т. д. В PHP же FastCGI имеет свои особенности, мы их рассмотрим чуть позже. Сейчас речь идет о не-PHP. |
FastCGI работает следующим образом: скрипт загружается в память, запускает некоторый тяжелый код инициализации (например, подключает объемистые библиотеки), а затем входит в цикл обработки входящих HTTP-запросов. Один и тот же процесс-скрипт обрабатывает несколько различных запросов один за другим, что отличается от работы в CGI-режиме, когда на каждый запрос создается отдельный процесс, "умирающий" после окончания обработки. Обычно, обработав N запросов, FastCGI-скрипт завершается, чтобы сервер перезапустил его вновь уже в "чистой песочнице".
Для ускорения обработки стартует, конечно, не один процесс-скрипт, а сразу несколько, чтобы каждый следующий запрос не ждал обработки предыдущего. Как число одновременно запускаемых процессов, так и количество последовательных запросов на один процесс можно задавать в конфигурации сервера. |
Старые скрипты, написанные с расчетом на CGI, приходится дорабатывать, чтобы они могли работать в FastCGI-окружении (касается использования FastCGI в Си, Perl и т. д.; на PHP дорабатывать не нужно, но у этого свои недостатки, см. ниже). Действительно, ведь раньше скрипт стартовал каждый раз "с чистого листа", а теперь ему приходится иметь дело с тем "мусором", который остался от предыдущего запроса. Если раньше CGI-скрипт на Perl выглядел как
| Листинг 1 |
use SomeHeavyLibrary; print "Hello, world!\n"; |
| Листинг 2 |
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-приложение.
Вероятно, вы слышали, что 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, даже не подозревают о том, что оно должно работать как-то по-другому...
Каждый раз, когда PHP-скрипт получает управление, PHP компилирует (точнее, транслирует) код скрипта во внутреннее представление (байт-код). Если файл небольшой, трансляция происходит очень быстро (Zend Engine в
Существует ряд инструментов, кэширующих в разделяемой оперативной памяти (shared memory) оттранслированное внутреннее представление PHP-кода. Таким образом, при повторном включении PHP-файла он уже не транслируется, а байт-код берется из кэша в памяти. Естественно, это значительно ускоряет работу.
Одним из таких инструментов является eAccelerator. Он устанавливается в виде расширения PHP (подключается в php.ini) и требует самой минимальной настройки. Рекомендую включить в нем режим кэширования байт-кода исключительно в оперативной памяти и отключить сжатие (установить параметры
Преимущество eAccelerator в том, что он работает весьма стабильно и быстро даже на больших объемах PHP-кода. У меня ни разу не возникало проблем с этим инструментом (в отличие от Zend Accelerator, к примеру).
Думаете, 5 МБ
Многие на этом месте посмеются и скажут, что не надо использовать Zend Framework и Propel, т.к. они "тормозные". Но действительность заключается в том, что тормозные вовсе даже не они... Плохую производительность имеет метод, который по умолчанию использует PHP для загрузки кода. К счастью, ситуацию нетрудно исправить.
Чтобы не быть голословным, я приведу результаты небольшого тестирования, которое я специально провел в "чистом" окружении, не привязанном к какому-либо конкретному проекту. Тестировалась скорость подключения всех файлов Zend Framework (за исключением Zend_Search_Lucene). Предварительно из кода были вырезаны все вызовы
Итак, всего подключались 790 PHP-файлов общим объемом 4.9 МБ. Немало, верно? Подключение осуществлялось примерно так:
| Листинг 3: Подключение всех 790 файлов ZF (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, вызов
Вот результаты тестирования с eAccelerator-ом и без на моем не очень мощном ноутбуке (Apache, mod_php):
Как видите, eAccelerator дает примерно двукратное ускорение на 790 файлах общим объемом 4.9 МБ. Слабовато. К тому же, 435
Ходят слухи, что PHP гораздо быстрее загружает один большой файл, чем десять маленьких того же суммарного объема. Я решил проверить это утверждение, объединив весь Zend Framework в один файл размером 4.9 МБ и подключив его единственным вызовом
Первая строчка говорит о том, что один большой файл размером 4.9 МБ и правда загружается быстрее, чем 790 маленьких: 458 мс против 911 мс (см. выше). Экономия в 2 раза.
А вот вторая строчка заставила меня от удивления подпрыгнуть на стуле и перепроверить результат несколько раз. Надеюсь, это же произойдет и с вами. Действительно, 42
Итак, мы действительно получили ускорение загрузки в 22 раза, как и было обещано в заголовке. Раньше весь Zend Framework, разбитый на файлы, подключался 911 мс, а с использованием eAccelerator и объединенем всех файлов в
Подведем итоги.
При всем при этом eAccelerator лишен основных проблем "настоящего" FastCGI-сервера. Скрипты избавлены от утечек памяти и запускаются в гарантировано "чистом" окружении. Вам также не надо следить за изменением кода и перезапускать сервер всякий раз, когда внесены модификации в мало-мальски глубинный код системы.
Заметьте также, что мы подключали весь Zend Framework. В реальных скриптах объем кода будет сильно меньше, т.к. обычно для работы требуется лишь незначительная часть ZF. Но даже при условии, что библиотеки занимают 4.9 МБ, мы получаем время загрузки 42
Конечно, если вы планируете запускать FastCGI в PHP не из соображений производительности, а просто чтобы не "завязываться" за Apache и ограничиться связкой "nginx+PHP" или "lighttpd+PHP", ничто этому не мешает. Более того, задействовав eAccelerator для FastCGI+PHP и слив много файлов кода в один большой, вы получите то же самое ускорение, которое я описал выше. Однако не тешьте себя надеждами, что ускорение дал именно FastCGI: это не так. Применяя mod_php+eAccelerator, вы достигли бы практически такого же результата, что и FastCGI+eAccelerator. |
Вручную объединять все файлы библиотек в
Также рекомендую вам отказаться от явного включения файлов по
Наконец, применяйте функцию set_include_path(), чтобы код подключения библиотек выглядел вот так:
| Листинг 4 |
require_once "Some/Library.php"; |
| Листинг 5 |
require_once LIB_DIR . "/Some/Library.php"; |
Константы, определяющие путь к директории библиотек явным образом, большое зло и усложнение программы. Они также косвенно противоречат стандартам кодирования Zend Framework и PEAR, которых я тоже рекомендую по возможности придерживаться.
Итак, хотите использовать "тяжелые" библиотеки в PHP-
Читайте также продолжение в следующей набле. Там рассказано о таких понятиях, как балансировка нагрузки, reverse proxy и "медленные клиенты".
|
![]() |
| ||||||||||||||||||||||||
| Дмитрий Котеров | 9 мая 2008 г. ©1999-2010 | | Контакт | Вернуться к оглавлению |