Форумы портала PHP.SU » PHP » Пользовательские функции » Измерение памяти и времени в PHP

Страниц (2): [1] 2 »
 

1. EuGen - 15 Ноября, 2013 - 10:13:10 - перейти к сообщению
Приветствую,

Представляю вашему вниманию простой инструмент, который предоставляет возможность тестировать функции - чтобы измерить время их исполнения/потребляемую память. Так же существует возможность определять собственную функцию для измерения, однако она, разумеется, должна возвращать числовые значения.
CODE (php):
скопировать код в буфер обмена
  1. namespace Benchmark;
  2.  
  3. final class Measure
  4. {
  5.     const MEMORY_VALUE      = 0;
  6.     const BENCHMARK_VALUE   = 1;
  7.     const BENCHMARK_AVERAGE = 2;
  8.     const BENCHMARK_COUNT   = 3;
  9.    
  10.     private $max, $memory;
  11.  
  12.     public function memoryTick()
  13.     {
  14.        $this->memory = memory_get_usage() - $this->memory;
  15.        $this->max    = $this->memory>$this->max?$this->memory:$this->max;
  16.        $this->memory = memory_get_usage();
  17.     }
  18.     /**
  19.      *
  20.      * @param callable $function A function to be measured
  21.      * @param array $args Parameters to be passed for measured function
  22.      * @return array Result currently contains one value: used memory space
  23.      */
  24.     public function benchmarkMemory(callable $function, array $args=[])
  25.     {
  26.        declare(ticks=1);
  27.        $this->memory = memory_get_usage();
  28.        $this->max    = 0;
  29.  
  30.        register_tick_function('call_user_func', [$this, 'memoryTick']);
  31.        $this->measureFunction($function, $args, 1);
  32.        unregister_tick_function('call_user_func');
  33.        return [
  34.           self::MEMORY_VALUE => $this->max
  35.        ];
  36.     }
  37.     /**
  38.      *
  39.      * @param callable $function A function to be measured
  40.      * @param array $args Parameters to be passed for measured function
  41.      * @param int $count Count of measurements
  42.      * @return array Result currently contains: total time, average time and measurements count
  43.      * @throws \InvalidArgumentException If measurements count is invalid
  44.      */
  45.     public function benchmarkTime(callable $function, array $args=[], $count=1)
  46.     {
  47.         return $this->benchmarkCustom('microtime', $function, [1], $args, $count);
  48.     }
  49.     /**
  50.      *
  51.      * @param callable $benchmark Function which will do measurements
  52.      * @param callable $function A function to be measured
  53.      * @param array $benchmarkArgs Parameters to be passed for measurement function
  54.      * @param array $functionArgs Parameters to be passed for measured function
  55.      * @param int $count Count of measurements
  56.      * @return array Result currently contains: total value, average value and measurements count
  57.      * @throws \InvalidArgumentException If measurements count is invalid
  58.      * @throws \LogicException If measurement function did not returned numeric value
  59.      */
  60.     public function benchmarkCustom(callable $benchmark, callable $function, array $benchmarkArgs=[], array $functionArgs=[], $count=1)
  61.     {
  62.         if(!is_int($count) || $count <=0)
  63.         {
  64.             throw new \InvalidArgumentException('Count of measure times must be positive integer');
  65.         }
  66.         $init   = call_user_func_array($benchmark, $benchmarkArgs);
  67.         if(!is_numeric($init))
  68.         {
  69.             throw new \LogicException('Benchmark function must return valid numeric value');
  70.         }
  71.         $this->measureFunction($function, $functionArgs, $count);
  72.         $end    = call_user_func_array($benchmark, $benchmarkArgs);
  73.         return [
  74.             self::BENCHMARK_VALUE    => $end - $init,
  75.             self::BENCHMARK_AVERAGE  => ($end - $init) / $count,
  76.             self::BENCHMARK_COUNT    => $count
  77.         ];
  78.     }
  79.    
  80.     private function measureFunction($function, $args, $count)
  81.     {
  82.         for($i=0; $i<$count; $i++)
  83.         {
  84.             $result = call_user_func_array($function, $args);
  85.         }
  86.     }
  87. }

- пример использования:
CODE (php):
скопировать код в буфер обмена
  1. spl_autoload_register(function($className)
  2. {
  3.     $path = explode('\\', $className);
  4.     include_once(end($path).'.php');
  5. });
  6.  
  7. $measure    = new \Benchmark\Measure;
  8.  
  9. $result     = $measure->benchmarkMemory('str_repeat', ['test', 1E4]);
  10. var_dump($result);
  11. $result     = $measure->benchmarkMemory('str_repeat', ['test', 1E3]);
  12. var_dump($result);
  13. $result     = $measure->benchmarkTime('uniqid', [1], 1000);
  14. var_dump($result);


Следует понимать, что инструмент предназначен для сравнительных измерений. Иначе говоря, есть две (или больше) функции и требуется понять, насколько первая лучше/хуже второй. Для этого данный инструмент и будет полезен. Он не предназначен для получения точных (абсолютных) значений используемой памяти/времени, так как использует много внутренних преобразований и вызовов, что служит причиной пусть и небольшой, но неточности.

Соответствующий репозиторий доступен на GitHub
2. esterio - 15 Ноября, 2013 - 11:43:26 - перейти к сообщению
а можно одновременно и вермя и память логировать
3. EuGen - 15 Ноября, 2013 - 11:47:17 - перейти к сообщению
esterio
Это разные измерения и потому они проводятся разными методами. Однако же ничто не мешает измерить время, затем память и после этого через array_merge() собрать всё в один массив (именно по этой причине флаги значений в классе сделаны различными)
4. Ch_chov - 15 Ноября, 2013 - 15:14:10 - перейти к сообщению
В PEAR есть похожий класс для бенчмарков, но память он не замеряет. Наверно это не так актуально.
http://pear.php.net/manual/ru/pa...ng.benchmark.php

Интересно, если $count < 100, то разброс в измерениях может быть огромным (больше 100%). Для эталонных тестов это нужно учитывать.

PHP:
скопировать код в буфер обмена
  1. <?PHP
  2.  
  3. require 'Measure.php';
  4. $bm = new \Benchmark\Measure;
  5.  
  6. $function = function($arg) {for(cosh($arg), $i = 0; $i < 1000; $i++);};
  7. $args = [60];
  8. $count = isset($argv[1]) ? (int) $argv[1] : 1;
  9.  
  10. $result_1 = $bm->benchmarkTime($function, $args, $count);
  11. $result_2 = $bm->benchmarkTime($function, $args, $count);
  12.  
  13. echo "Measure:\n", $result_1[1], "\n", $result_2[1], "\n";
  14. printf("Diff: %.2f%%\n", ($result_1[1]  - $result_2[1]) / $result_2[1] * 100);
  15.  
  16. require 'Benchmark/Iterate.php';
  17. $bi = new Benchmark_Iterate;
  18. $bi->run($count, $function, $args[0]);
  19. $result_1 = $bi->get();
  20.  
  21. $bi = new Benchmark_Iterate;
  22. $bi->run($count, $function, $args[0]);
  23. $result_2 = $bi->get();
  24.  
  25. echo "\nBenchmark_Iterate:\n", $result_1['mean'], "\n", $result_2['mean'], "\n";
  26. printf("Diff: %.2f%%\n", ($result_1['mean']  - $result_2['mean']) / $result_2['mean'] * 100);


Цитата:
$ php test.php 100
Measure:
0.010824918746948
0.011712074279785
Diff: -7.57%

Benchmark_Iterate:
0.000149
0.000135
Diff: 10.37%
5. EuGen - 15 Ноября, 2013 - 15:54:04 - перейти к сообщению
Вообще, измерение памяти для моих задач было весьма критичным моментом. При этом дело сводилось зачастую к таким вещам, как проверка копирования/передачи по ссылке. При этом использовать внешний профилировщик было неудобно из-за большого объема мелких правок при оптимизации.

Кроме того, данный инструмент успешно использовался для измерений используемого файлового пространства для функций, оперирующих с ФС. Насчёт времени - это верно. Как правило, 1E6 - минимальный порог определения достоверности измерения.
6. Ch_chov - 15 Ноября, 2013 - 16:38:50 - перейти к сообщению
EuGen пишет:
данный инструмент успешно использовался для измерений используемого файлового пространства для функций, оперирующих с ФС
Каким образом?
7. EuGen - 15 Ноября, 2013 - 16:41:16 - перейти к сообщению
Ch_chov пишет:
Каким образом?

Передачей измеряющего callback, в котором выполняется что-нибудь наподобие system() + df
8. Ch_chov - 15 Ноября, 2013 - 16:46:46 - перейти к сообщению
А зачем тики? Почему не memory_get_peak_usage?
9. EuGen - 15 Ноября, 2013 - 17:28:41 - перейти к сообщению
Ch_chov пишет:
А зачем тики? Почему не memory_get_peak_usage?


Потому что:
PHP:
скопировать код в буфер обмена
  1.  
  2. function foo($count)
  3. {
  4.    $result = str_repeat('test', $count);
  5. }
  6.  
  7. foo(1E5);
  8. var_dump(memory_get_peak_usage()); //something like 624824
  9. foo(1E3);
  10. var_dump(memory_get_peak_usage()); //still 624824 cause peak was for 1E5

- то есть пропадает возможность динамического использования, а если это так, то с таким же успехом можно использовать профилировщик.
10. Hapson - 15 Ноября, 2013 - 17:35:52 - перейти к сообщению
Я делаю так
PHP:
скопировать код в буфер обмена
  1.  
  2. $start = microtime(true); $memory = memory_get_usage(false);
  3.  
  4. /*
  5. * какой-то код
  6. */
  7.  
  8. echo 'Time: '. number_format((microtime(true)-$start), 3) .' sec.<br />';
  9. echo 'Memory: '. number_format((memory_get_usage(false)-$memory)/1024, 3) .' Kb.<br />';
  10. echo 'Max Memory: '. number_format(memory_get_peak_usage(false)/1024, 3) .' Kb.<br />';
  11.  

Не пойму, зачем для этого писать столько кода...
11. EuGen - 15 Ноября, 2013 - 17:38:22 - перейти к сообщению
Hapson пишет:
Не пойму, зачем для этого писать столько кода...

В Вашем случае и тот код, что Вы привели - не нужен, поскольку верным инструментом будет профилировщик. Кроме того, Вы никогда не сможете таким способом измерить память.
12. Hapson - 15 Ноября, 2013 - 17:43:25 - перейти к сообщению
EuGen пишет:
Вы никогда не сможете таким способом измерить память

Однако могу
http://clip2net[dot]com/s/6bkYxB
13. EuGen - 15 Ноября, 2013 - 17:50:06 - перейти к сообщению
Hapson пишет:
Однако могу
http://clip2net[dot]com/s/6bkYxB

Однако, нет:
http://codepad[dot]viper-7[dot]com/QSlOyT

Все три значения ошибочны, потому что:

- Время нельзя измерить одной итерацией. Как уже сказано выше, для этого требуется как минимум 1E6 повторений
- "Память" (Memory) измерена неверно, так как в глобальном контексте переменная внутри функции foo() не будет учтена, однако там строка длиной 4*1E4 байт
- "Максимальная память" (Max Memory) измерена неверно, так как до старта теста есть контекст, который использует больше памяти, чем тестируемая функция.
14. esterio - 15 Ноября, 2013 - 17:50:26 - перейти к сообщению
Hapson
Здесь идет потрбление памяти конкретной функции, а не скрипта в целом
15. Hapson - 15 Ноября, 2013 - 18:02:15 - перейти к сообщению
esterio пишет:
Hapson
Здесь идет потрбление памяти конкретной функции, а не скрипта в целом

Все равно не пойму...
Если мне нужно сравнить функции, то делаю примерно так

PHP:
скопировать код в буфер обмена
  1.  
  2. <?PHP
  3. function foo(){
  4.         $result = str_repeat('test', 1E4);
  5. }
  6.  
  7. $start = microtime(true); $memory = memory_get_usage(false);
  8.  
  9. for($z = 0; $z < 100000; $z++){
  10.         foo();
  11. }
  12.  
  13. echo 'Time: '. number_format((microtime(true)-$start), 3) .' sec.<br />';
  14. echo 'Memory: '. number_format((memory_get_usage(false)-$memory)/1024, 3) .' Kb.<br />';
  15. echo 'Max Memory: '. number_format(memory_get_peak_usage(false)/1024, 3) .' Kb.<br />';
  16. ?>
  17.  


http://clip2net[dot]com/s/6blBgF

 

Powered by ExBB FM 1.0 RC1