Разработка

Оптимизация PHP кода: 7 принципов

Производительность решений на PHP — частая тема различных споров и дискуссий. Мы не будем сейчас участвовать в них. Ведь как бы там ни было, все всегда зависит от конкретной задачи. К примеру, мне известен достоверный случай, когда некий программный код в течение полутора года переписывали на Ассемблере. Изначально он был написан на Си. Когда работы завершились, позади остались сотни рабочих дней большой группы разработчиков, а на руках — версия ПО, полностью написанная на Ассемблере. Какое же было удивление команды, когда в итоге их творение на Ассемблере заработало гораздо медленней их же, более раннего творения на Си!

Оптимизация программного кода — задача многогранная. Следует учитывать множество факторов. Следует понимать, что в ряде случаев ею вообще можно пренебречь: в конце концов, часто проще докупить высокопроизводительного «железа», чем оплачивать дополнительную работу команды высококлассных разработчиков. Автоматизация, индустриализация, други мои! Роботы заменяют людей. А ведь когда-то были такие профессии, как, например, профессия фонарщика или лифтера, нет, не того, который сейчас чинит и обслуживает лифты. Того, который раньше был штурманом лифта и возил пассажиров куда они просили. Сейчас это уже кажется смешным. А лет через энное количество смех будут вызывать пилоты самолетов. Те будут летать на полностью автоматизированных системах управления И т.д.

Так или иначе, нет смысла оптимизировать очень простые, либо редко используемые части программного обеспечения. По крайней мере до того момента, пока все остальное не в полном порядке. А на что именно следует обратить внимание в первую очередь? Сейчас мы об этом и будем говорить.

Рассмотрим на примере PHP. На самом деле, что бы там не говорили о нем, PHP ничуть не хуже Perl или ASP.NET, я бы сказал даже наоборот — у PHP значительно больше преимуществ. Почему же ему чаще всех достается? Очень просто — потому, что он самый популярный! Ведь и Windows ругают все, под нее больше всего вирусов, в ней больше всего найденных «дыр». Но стоит посмотреть на ее популярность… Хм более 98% пользователей работают именно под ОС Windows. На все остальные вместе взятые операционные системы приходится не более 2% пользователей. Не удивительно, что к Windows такое внимание. Я уверен, что если любая другая ОС была бы настолько же популярна (и настолько же сложна, функциональна и доступна), в ней было бы найдено не меньше дыр. Ну да ладно, это разговор для отдельной темы.

Возвращаясь к PHP, кроме всего прочего, это, пожалуй, наиболее простой язык программирования, широко применяемый для веба. Его простота и доступность обусловливают возникшую армию новичков, которые хоть что-то в нем пытаются делать. Их некомпетентность часто становится причиной негативных дискуссий о PHP. Тот же ASP.NET не столь доступен. Как следствие — основные его «носители» — достаточно грамотные люди. Со всеми вытекающими последствиями. Но ведь доступность PHP никоим образом не нивелирует его достоинств в руках профессионалов.

В конце концов, на чем написана самая популярная социальная сеть, и второй по посещаемости веб-ресурс в мире, facebook? А его аналог vkontakte? Конечно же, на PHP!

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

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

7 принципов, или 7 позиций оптимизации кода PHP.

1) Любовь к готовым библиотекам.

Различные готовые библиотеки — вещи, безусловно, полезные и удобные. Но следует не забывать, что бесплатный сыр бывает только в мышеловке. За все, так или иначе, а платить приходится.

Разработка программного обеспечения для реализации задумки некоего сайта всегда (при условии соотносимых уровней исполнителей, конечно же) в плане производительности будет значительно более выгодна, чем применение готовой, универсальной CMS (Content Management System — система управления содержимым, «движок» сайта). Низкая производительность — расплата за универсальность CMS. Это то — же самое, что ездить на базар на личном автобусе, или даже, на самолете, но не на своем легковом автомобиле. То же самое и с готовыми библиотеками.

Но почему я поставил библиотеки на первое место? Потому, что если они применяются, то сразу съедают львиную долю производительности. Смотрите сами: подключение одних только Smarty и DbSimple сразу же «съедает» порядка 0.025 сек. времени и почти 3/4 Мб ОЗУ. И это на неплохом сервере при низкой общей нагрузке. Добавить сюда еще какой-нибудь phpMailer, и мы имеем 0.03 сек. затраченного времени и 1 Мб сожранной памяти попросту на пустом месте. Мы еще ничего не делали, лишь подключили модули, это еще даже без их инициализации! У меня есть один самописный проект, наподобие социальной сети, так в нем на полное создание странички в среднем уходит ощутимо меньше ресурсов. Другой пример — движок новостного сайта, который я разрабатывал: публикации, комментарии и т.д. По большому счету, ничего сверхсложного, но и скорость достойная — в среднем меньше 0.001 сек. на генерацию странички и до 0.15 Мб памяти. Проекты построены на связке PHP + MySQL. Кстати, таким образом, второй из них, спокойно держит более 400тыс. хитов в сутки, со всплесками до 1600 хитов за минуту в пиках и почти 500 хостами онлайн. На VPS за $30/мес. А подключи к нему те три библиотеки, тогда уже не обойтись без выделенного сервера, и не самой слабой конфигурации. А ведь дело нередко доходит до применения с десятка готовых библиотек.

С другой стороны, безусловно, если вы разрабатываете небольшой сайт — для фирмы, или личную страничку, заботиться об оптимизации, и, таким образом, отказываться от применения готовых библиотек тут смысла нет. Время дороже. Если посещаемость вашего сайта не более 1000 посетителей в сутки, не стоит тратить время на идеальный и быстрый код. Пользуйтесь готовыми библиотеками. Вам хватит ресурсов любого хорошего виртуального хостинга. Если у вас посещаемость достигает 2-3 тыс. посетителей в сутки, уже есть смысл задуматься об оптимизации кода. Но все же следует хорошо подумать над тем, стоит ли вам на данном этапе отказываться от готовых библиотек и убивать время на переработку кода (либо же время и деньги на программиста, который это сделает). Зачастую проще просто купить дополнительных ресурсов, или перейти на VPS, оплачивая по $10-50 в месяц.

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

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

2) Игнорирование Zend Optimizer, eAccelerator

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

3) «Кривое» применение баз данных

По сути это отдельная тема. Вам следует помнить, что каждое подключение к БД, каждый запрос, каждая лишняя таблица в запросе и т.д. — все это создает ощутимую нагрузку. Но все же, логика самой БД, а не интерфейса работы с ней, несет наибольшую нагрузку. Мне недавно приходилось оптимизировать один очень популярный модуль для одного из самых популярных «движков» форума. Как мне сразу удалось выяснить — тот самый модуль и был главной причиной существенных тормозов. Добавив одну небольшую таблицу и два новых SQL запроса в PHP код, производительность форума была повышена на 60-80% (в зависимости от странички), потребление памяти снизилось на 5-7%! И это все практически на ровном месте. Как видите — это тот случай, когда добавление «лишней» таблицы и «лишних» запросов существенно увеличивает производительность. Я пошел не экстенсивным, а интенсивным путем, улучшив логику. И получив поистине замечательный результат.

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

Итак, прежде всего, всегда используйте файлы вместо БД, если в этом есть смысл. Когда в этом есть смыл? Предположим, вы пишите движок сайта новостей. Новость добавляется туда раз и навсегда. Она не подвержена последующим изменениям, она большая и цельная. Намного проще и эффективней поместить ее в отдельный файл, а не в БД. А вот рецензию к новости — уже лучше в БД. Ведь на определенных страничках вам явно придется выводить список последних рецензий по теме, или список случайных рецензий. Если писать каждую рецензию в отдельный файл — то при выводе, скажем, 50 рецензий на страничке, нам придется подключать к работе столько же соответствующих файлов рецензий. Писать все рецензии в один и тот же файл тоже не очень хорошо: при их большом количестве файл будет очень большим, и только разрастаться со временем, производительность же, соответственно — снижаться. Да и это попросту может быть достаточно неудобным и небезопасным делом — работа с такими файлами. Другое дело «закидать» наши небольшие рецензии в БД и «выдернуть» нужные одним несложным запросом. То же самое относится к любым часто редактируемым данным. Например, писать счетчик просмотров новостей на файлах — большая глупость. Только БД.

Что касается соединения с БД, его стоит закрывать как только в нем отпадает потребность. Однако, не стоит этого делать, если в работе данного скрипта оно еще пригодится.

Следите за содержимым переменных, которым присваиваются результаты выполнения запросов. Есть смысл использовать unset в ситуациях, когда серьезные объемы данных более не нужны.

4) Отсутствие кэширования

В целом ряде случаев кэширование просто незаменимо. Рассмотрим простой пример. Чуть выше, когда речь шла о БД, я приводил пример со списком рецензий. Предположим, мы заходим на некую страничку, где отображается список из 50 рецензий к последним новостям. Как правило, всех их можно выбрать из БД одним несложным запросом. Но с другой стороны, мы можем заранее создать некий файл, который будет содержать все эти 50 рецензий в готовом виде. Нам не нужно будет выбирать их из БД, форматировать согласно нашему шаблону и вставлять в нужное место в страничке. Мы просто сразу подключаем все это из заранее сформированного файла.

Суть кэширования сводится к созданию кэша из наиболее часто используемых и наиболее редко изменяемых данных. К примеру, как часто вам придется обновлять кэш ваших 50 рецензий? Ясно, что каждый раз, когда будут добавляться новые новости — чтоб наш кэш отображал актуальную информацию. А как часто будут добавляться новые новости? Предположим, примерно каждые 15 минут, т.е. до 100 раз за сутки. А какая посещаемость у этого сайта, как часто просматривают эти рецензии? На проекте, «движок» которого я разрабатывал, порядка 70 тыс. раз за сутки. Таким образом, вместо того, чтобы выполнять выборку из базы, форматирование и вставку этих рецензий по 70 тыс. раз на сутки, она выполняется обычно не более 100 раз, остальное — лишь простые инклуды полностью готовых данных.

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

Не забывайте о том, что кэш не обязательно хранить только в файлах и в нем не обязательно можно хранить только части нашей будущей HTML — странички. А особенно если он подвержен частым, систематическим обновлениям. Например, список данных о пользователях, находящихся онлайн, гораздо разумней держать в БД, в таблице типа Memory.

5) Буфер, сжатие, фронтенд http-сервер

Все очень просто. В процессе выполнения все данные, которые выводятся пользователю, тут же ему и отправляются. Грубо говоря, пока данные не будут забраны, продолжение выполнения кода не выполняется. Так что если в процессе работы ваш скрипт отправляет готовый HTML — код через функцию echo, например, или любым другим методом, то крипт передает данные «по кусочкам», каждый раз «подвисая». Намного грамотней выводить данные в буфер, примерно так:
php

//инициализируем буферизацию
ob_start
();

//выполнение кода скрипта
//...

//завершаем буферизацию, извлекаем данные
$html
= ob_get_contents();
ob_end_clean
();
exit($html);

?>


Включение динамического сжатия готовых HTML — страниц (ob_start(`ob_gzhandler`);) может сжать их в среднем на 20-60%, увеличивая скорость загрузки и снижая количество «висящих» процессов. У вас ограничение по трафику на хостинге? Тогда динамическое сжатие страниц принесет двойную выгоду.

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

В завершение, фронтенд http-сервер может вывести производительность ваших PHP скриптов на новый уровень. Дело в том, что даже с кэшированием, процессы, связанные с запросом пользователя, будут «висеть» до того, пока пользователь не примет все данные (либо пока не выйдет лимит отведенного на это времени). Суть фронтенд сервера в том, чтобы сразу же принимать данные, позволяя сразу выгружать отработавшие процессы. Он является неким связующим звеном между пользователем и достаточно «тяжелым» бэкенд сервером — где выполняются ваши PHP скрипты. Но благодаря своей «легкости», фронтенд сервер существенно снижает потребляемые ресурсы. Опять-таки, это очень обширная тема и мы будем рассматривать ее более детально, в ближайшем будущем.

6) Тотальный инклуд

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

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

Не пренебрегайте оптимизацией инклудов. Выносите то, что применяется не повсеместно в отдельные, специализированные инклуды. Еще один пример для закрепления. Мой любимый движок новостного сайта. Функции добавления новостей, комментариев, их контроля и пре-парсинга занимает около 1/3 кода общего количества функций. Но как часто они необходимы? В 1 из 100 запросах? Или в 1 из 200? Или еще реже? Так зачем же их каждый раз грузить? Это действительно может существенно снизить производительность ваших решений.

7) Лишняя функциолизация и ООП-тимизм

Функции необходимо применять только тогда, когда это действительно имеет смысл. Давайте вспомним базовую цель функций. Это не что иное, как вынос часто используемых в пределах одного цикла работы программы частей кода в отдельную, общую часть. В принципе, выносить код в «отдельную часть» имеет смысл и тогда, когда он выполняется по некому, относительно маловероятному событию. К примеру, прикрепление фото к посту на форуме. Вынос обработчика загрузки и обработки картинки в «отдельную часть» позитивно отразится на производительности, даже если он заключен в условие (факт загрузки картинки). При этом, если данная «отдельная часть» нам понадобится лишь один раз, лучше выполнить ее просто как код (инклуд), а не как функцию. Функции всегда требуют некоторых дополнительных ресурсов. То же самое касается и ООП-программирования в PHP. ООП-код потребляет несколько больше ресурсов, поэтому если вам не принципиально ООП-форматирование кода, лучше отказаться от него. Я говорю именно о ООП-форматировании, поскольку ООП-подход в PHP имеет мало общего с самим понятием ООП-программирования. Как и PHP с классическим пониманием языков программирования и их задач — о чем я писал в начале.

P.S.

Вот вы и ознакомились с 7 основными принципами производительного кода на PHP. Это только — только самое начало. Впереди у нас еще много интересного, более конкретного материала по данной теме.

Автор: VReal