Разработчику. Сборник рецептов PHP
Задавайте вопросы

Профилирование программы

Вернуться назад

Задача

Есть фрагмент программы, и необходимо провести его исследование, чтобы определить время выполнения каждого оператора.

Решение

Для этого предназначен модуль PEAR Benchmark:

require 'Benchmark/Timer.php';

$timer =& new Benchmark_Timer(true);

$timer -> start();
// некоторый установочный код
$timer -> setMarker('setup');
// еще часть исполняемого кода
$timer -> setMarker('middle');
// еще одна часть исполняемого кода
$timer -> setmarker('done');
// и наконец последняя часть исполняемого кода
$timer -> stop();

$timer -> display();

Обсуждение

Вызов функции etMarker() записывает время. Метод display() выводит на печать список маркеров, время их установки и время, прошедшее с момента установки предыдущего маркера:

маркер время установки прошедшее время проценты
Start 1029433375.42507400 - 0.00%
setup 1029433375.42554800 0.00047397613525391 29.77%
middle 1029433375.42568700 0.00013899803161621 8.73%
done 1029433375.42582000 0.00013303756713867 8.36%
Stop 1029433375.42666600 0.00084602832794189 53.14%
total - 0.0015920400619507 100.00%

Модуль Benchmark также включает класс Benchmark_Iterate, позволяющий определить время многократного выполнения одной функции:

require 'Benchmark/Iterate.php';

$timer =& new Benchmark_Iterate;

// простая функция определения времени выполнения
function use_preg($ar) {
     for ($i = 0, $j = count($ar); $i < $j; $i++) {
          if (preg_match('/gouda/',$ar[$i])) {
               // it's gouda

          }

     }

}

// еще одна простая функция определения времени выполнения
function use_equals($ar) {
     for ($i = 0, $j = count($ar); $i < $j; $i++) {
          if ('gouda' == $ar[$i]) {
               // it's gouda

          }

     }

}

// запускаем функцию use_preg() 1000 раз
$timer -> run(1000,'use_preg',
               array('gouda','swiss','gruyere','muenster','whiz'));
$results = $timer -> get();
print "Mean execution time for use_preg(): $results[mean]\n";

// запускаем функцию use_equals() 1000 раз
$timer -> run(1000,'use_equals',
               array('gouda','swiss','gruyere','muenster','whiz'));
$results = $timer -> get();
print "Mean execution time for use_equals(): $results[mean]\n";

Метод Benchmark_Iterate::get() возвращает ассоциативный массив. Элемент mean этого массива содержит значение времени выполнения каждой итерации функции. Элемент iterations содержит количество итераций. Время выполнения каждой итерации хранится в элементе массива с целочисленным ключом. Например, время первой итерации находится в элементе $results[1], а время 37-й итерации находится в элементе $results[37].

Для автоматической записи времени, прошедшего после выполнения каждой строки программы, применяется конструкция declare с параметром ticks:

function profile($display = false) {

     static $times;

     switch ($display) {
     case false:
          // добавляем текущее время к списку записанных значений времени
          $times[ ] = microtime();
          break;
     case true:
          // возвращаем прошедшее время в микросекундах
          $start = array_shift($times);
          $start_mt = explode(' ', $start);
          $start_total = doubleval($start_mt[0]) + $start_mt[1];

          foreach ($times as $stop) {
               $stop_mt = explode(' ', $stop);
               $stop_total = doubleval($stop_mt[0]) + $stop_mt[1];
               $elapsed[ ] = $stop_total - $start_total;

     }

          unset($times);
          return $elapsed;
          break;

     }

}
// регистрируем обработчик тактов
register_tick_function('profile');

// определяем время старта
profile( );

// выполняем код, записывая время выполнения каждого оператора
declare (ticks = 1) {
          foreach ($_SERVER['argv'] as $arg) {
               print strlen($arg);

          }

}
// печатаем прошедшее время
$i = 0;
foreach (profile(true) as $time) {
     $i++;
     print "Line $i: $time\n";

}

Параметр ticks позволяет многократно выполнять функцию для блока кода. Число ticks показывает, сколько тактов должно пройти между вызовами функции, зарегистрированной с помощью register_tick_function().

В предыдущем примере мы регистрируем единственную функцию и выполняем функцию profile() для каждого оператора внутри блока declare. Если в массиве $_SERVER['argv'] два элемента, то функция profile() выполняется четыре раза: одно выполнение при каждом проходе цикла foreach и один раз при каждом выполнении строки printstrlen($arg).

Можно также определить вызов двух функций на каждые три оператора:

register_tick_function('profile');
register_tick_function('backup');

declare (ticks = 3) {
     // code...

}

Можно также передать дополнительные параметры в зарегистрированные функции, которые могут быть методами объекта вместо обычных функций:

// передаем "parameter" в функцию profile()
register_tick_function('profile', 'parameter');

// вызываем $car -> drive();
$car = new Vehicle;
register_tick_function(array($car, 'drive'));

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

Для удаления функции из списка тактовых функций надо вызвать функцию unregister_tick_function():

unregister_tick_function('profile');

См. также

Информацию о классе PEAR Benchmark на
http://pear.php.net/package-info.php?package=Benchmark; документацию по функции register_tick_function() на http://www.php.net/register-tick-function, по функции unregister_tick_function() на http://www.php.net/ unregister-tick-function и по конструции declare на http://www.php.net/declare.

Вернуться назад

Рейтинг@Mail.ru

Яндекс.Метрика

Индекс цитирования

Рейтинг Сайтов ДОСКИ.РУ