Профилирование скриптов php

Это небольшая инструкция о том, как выяснить узкое место в программе, которое долго обрабатывается.

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

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

В писке узкого места поможет xdebug. Установим есго:

pecl install xdebug

Можно так же установить в ручную:


wget xdebug.org/link.php?url=xdebug201
tar -xzf xdebug-2.0.1.tgz
cd xdebug-2.0.1
phpize ./configure --enable-xdebug --with-php-config=/usr/bin/php-config
make
cp modules/xdebug.so /usr/lib/apache2/modules/xdebug.so

После установки необходимо отредактировать файл php.ini


xdebug.profiler_enable = On
xdebug.profiler_enable_trigger = On
xdebug.profiler_output_dir='/var/www/html'
xdebug.profiler_output_name='%h_%r'

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

Теперь нам понадобится программа для просмотра дампов. Для linux это KCachegrind. Можно найти в своем репозитории. Пользователям windows надо помучить гугл с поиском программы WinCacheGrind

Теперь, когда все приготовления сделаны, можно перезагрузить страницу с долгим скриптом. В папке, указанной в параметре xdebug.profiler_output_dir должен появиться файл с дампом, который надо открыть в KCachegrind
профилировка работы скрипта На скриншоте видно что блок main вызывает 3 функции. На самом деле больше, но показаны самые нагруженные. У блоков с вызываемыми функциями в процентах указано кто сколько нагружает. В данном примере видно что дольше всего отрабатывает функция CIBlockElement::GetList. Это вызов API Битрикса, для получения списка элементов инфоблока. Откроем на редактирование страницу и обычным поиском найдем где идет вызов данной функции.

Исследуемая страница содержит вывод списка пользователей Битрикса. Но помимо таблицы пользователей, в системе имелась сущьность, связанная с пользователем. Медленный код выглядел так:

 
<?while($rsUsers->NavNext(true, "f_")) :?>        
	<?        
	$user = [
		'rid'=>$f_UF_EXT_ID,
		'name'=>$f_LAST_NAME.' '.$f_NAME,
		];
	$votes = CIBlockElement::GetList(
		($by = "ID"), 
		array('PROPERTY_C'=>$f_ID,'IBLOCK_ID'=>13), 
		false, 
		false, 
		array('ID', 'PROPERTY_CANCELED', 'PROPERTY_c')
	)->fetch();        
	if($votes['PROPERTY_CANCELED_ENUM_ID'] == 3){            
		$list1[] = $user;        
	}else{            
		$list2[] = $user;        
	}        
	?>    
<?endwhile;?>

Как видно из кода, функция CIBlockElement::GetList вызывается в цикле. И если $rsUsers содержит в себе пару пользователей, то ничего страшного не произойдет. В моем же случае переменная содержала много записей (чуть меньше тысячи). Если проанализировать код, то можно понять, что выборка нужна для сортировки пользователей по двум массивам, в зависимости от параметров, хранящихся в другом инфоблоке.

Данный пример я считаю огромным камнем в огород Битрикса, который не умеет по другому работать со связями в моделях. Для начала я попробовал вынести результат выборки медленной функции в статический массив. Результаты не заставили себя ждать:
снижение времени работы скрипта в 3 раза время генерации снизилось примерно в 3 раза! Значит дело за нормализацией базы данных. У нас есть инфоблок с параметрами пользователя и таблица пользователей. Если добавить пользователю дополнительное поле и фильтровать по нему, то эффект будет таким же, за исключением того, что снизится расход ОЗУ


<?while($usr = $rsUsers->NavNext(true, "f_"))) :?> 
       <?        
	$user = [            
		'rid'=>$f_UF_EXT_ID,            
		'name'=>$а_LAST_NAME.' '.$а_NAME,        
	];        
	if($f_UF_С){            
		$list1[        

Теги:

битрикс php