Профилирование PHP скриптов и не много про оптимизацию

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

Буду рассказывать на примере моей работы над серверной частью социальной игры, где я занимался полностью всем сервером (от установки линукса, до настройки всего софта и написания кода).

Когда я только начал писать код (это было давненько), я мало, что знал про хайлоад, про то какие лимиты могут быть на сервере, про то, как оптимизировать код, я даже об этом не задумывался.

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

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

Именно поэтому для выявления пределов сервера надо проводить распределенные стресс тесты (множество запросов с множества машин за короткое время), которые наиболее адекватно могут показать "грузоподъемность" вашего приложения на данном железе.

Код, как таковой узким местом практически никогда не становится в средне статистическом веб проекте. Если конечно у вас нет очень сложной логики. Но тем не менее его оптимизировать тоже надо.

Итак, после небольшого экскурса в производительность мне стало интересно, сколько же времени выполняются мои скрипты? Решено было производить замеры скорости.

Я сделал таблицу в базе script_time_work с полями id, script_name, time_work.
То есть таблица призвана фиксировать имя скрипта и время его выполнения.
В начале каждого скрипта у меня был старт фиксирования времени выполнения в микросекундах $time_start = microtime(1), а в конце скрипта расчет времени выполнения microtime(1) - $time_start (текущее время минус время начала выполнения, получаем разницу, которая равна времени выполнения скрипта) и заносил это все в таблицу script_time_work.

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

Первая моя серьезная оптимизация заключалась в вычищении кода от нотисов. Всегда при разработке всегда включайте полное отображение ошибок, варнингов и нотисов error_reporting = E_ALL | E_STRICT. Генерация нотисов отнимает довольно много времени, кроме того это сделает ваш код более чистым, хотя бы с точки зрения PHP.

Далее я решил, что полезно бы разграничить время выполнения чистой PHP логики, и время выполнения SQL запросов.
Теперь таблица script_time_work стала выглядеть так id, script_name, time_work, sql_time

То есть я добавил поле, которое содержит время выполнения sql запросов. Собственно time_work - sql_time = время работы чистого php.

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

Собственно в ней я сделал тоже самое, что и в скрипте, в начале функции замер времени, в конце суммирование sql времени, а в конце скрипта все заносим в таблицу.

Кстати функция query еще выполняла одну полезную функцию - она логгировала в отдельной таблице все sql ошибки. Скрипт, sql код, ошибку, данные и дату. Так можно было всегда мониторить какие то sql баги.

После этого стало довольно очевидно, что процентов 70-90 времени выполнения скрипта занимают sql запросы. И если какие то скрипты работали больше ожидаемого времени, то нужно было провести более точный анализ каждого sql запроса в этом скрипте, что тоже можно было делать автоматически, просто занося время выполнения каждого скрипта в отдельную таблицу.

Оптимизация sql запросов это тема для отдельного материала.

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


comments powered by Disqus