123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- <?php
- /**
- * Codebench — A benchmarking module.
- *
- * @package Kohana/Codebench
- * @category Base
- * @author Kohana Team
- * @copyright (c) Kohana Team
- * @license https://koseven.ga/LICENSE.md
- */
- abstract class Kohana_Codebench {
- /**
- * @var string Some optional explanatory comments about the benchmark file.
- * HTML allowed. URLs will be converted to links automatically.
- */
- public $description = '';
- /**
- * @var integer How many times to execute each method per subject.
- */
- public $loops = 1000;
- /**
- * @var array The subjects to supply iteratively to your benchmark methods.
- */
- public $subjects = [];
- /**
- * @var array Grade letters with their maximum scores. Used to color the graphs.
- */
- public $grades = [
- 125 => 'A',
- 150 => 'B',
- 200 => 'C',
- 300 => 'D',
- 500 => 'E',
- 'default' => 'F',
- ];
- /**
- * Constructor.
- *
- * @return void
- */
- public function __construct()
- {
- // Set the maximum execution time
- set_time_limit(Kohana::$config->load('codebench')->max_execution_time);
- }
- /**
- * Runs Codebench on the extending class.
- *
- * @return array benchmark output
- */
- public function run()
- {
- // Array of all methods to loop over
- $methods = array_filter(get_class_methods($this), [$this, '_method_filter']);
- // Make sure the benchmark runs at least once,
- // also if no subject data has been provided.
- if (empty($this->subjects))
- {
- $this->subjects = ['NULL' => NULL];
- }
- // Initialize benchmark output
- $codebench = [
- 'class' => get_class($this),
- 'description' => $this->description,
- 'loops' => [
- 'base' => (int) $this->loops,
- 'total' => (int) $this->loops * count($this->subjects) * count($methods),
- ],
- 'subjects' => $this->subjects,
- 'benchmarks' => [],
- ];
- // Benchmark each method
- foreach ($methods as $method)
- {
- // Initialize benchmark output for this method
- $codebench['benchmarks'][$method] = ['time' => 0, 'memory' => 0];
- // Using Reflection because simply calling $this->$method($subject) in the loop below
- // results in buggy benchmark times correlating to the length of the method name.
- $reflection = new ReflectionMethod(get_class($this), $method);
- // Benchmark each subject on each method
- foreach ($this->subjects as $subject_key => $subject)
- {
- // Prerun each method/subject combo before the actual benchmark loop.
- // This way relatively expensive initial processes won't be benchmarked, e.g. autoloading.
- // At the same time we capture the return here so we don't have to do that in the loop anymore.
- $return = $reflection->invoke($this, $subject);
- // Start the timer for one subject
- $token = Profiler::start('codebench', $method.$subject_key);
- // The heavy work
- for ($i = 0; $i < $this->loops; ++$i)
- {
- $reflection->invoke($this, $subject);
- }
- // Stop and read the timer
- $benchmark = Profiler::total($token);
- // Benchmark output specific to the current method and subject
- $codebench['benchmarks'][$method]['subjects'][$subject_key] = [
- 'return' => $return,
- 'time' => $benchmark[0],
- 'memory' => $benchmark[1],
- ];
- // Update method totals
- $codebench['benchmarks'][$method]['time'] += $benchmark[0];
- $codebench['benchmarks'][$method]['memory'] += $benchmark[1];
- }
- }
- // Initialize the fastest and slowest benchmarks for both methods and subjects, time and memory,
- // these values will be overwritten using min() and max() later on.
- // The 999999999 values look like a hack, I know, but they work,
- // unless your method runs for more than 31 years or consumes over 1GB of memory.
- $fastest_method = $fastest_subject = ['time' => 999999999, 'memory' => 999999999];
- $slowest_method = $slowest_subject = ['time' => 0, 'memory' => 0];
- // Find the fastest and slowest benchmarks, needed for the percentage calculations
- foreach ($methods as $method)
- {
- // Update the fastest and slowest method benchmarks
- $fastest_method['time'] = min($fastest_method['time'], $codebench['benchmarks'][$method]['time']);
- $fastest_method['memory'] = min($fastest_method['memory'], $codebench['benchmarks'][$method]['memory']);
- $slowest_method['time'] = max($slowest_method['time'], $codebench['benchmarks'][$method]['time']);
- $slowest_method['memory'] = max($slowest_method['memory'], $codebench['benchmarks'][$method]['memory']);
- foreach ($this->subjects as $subject_key => $subject)
- {
- // Update the fastest and slowest subject benchmarks
- $fastest_subject['time'] = min($fastest_subject['time'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['time']);
- $fastest_subject['memory'] = min($fastest_subject['memory'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['memory']);
- $slowest_subject['time'] = max($slowest_subject['time'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['time']);
- $slowest_subject['memory'] = max($slowest_subject['memory'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['memory']);
- }
- }
- // Percentage calculations for methods
- foreach ($codebench['benchmarks'] as & $method)
- {
- // Calculate percentage difference relative to fastest and slowest methods
- $method['percent']['fastest']['time'] = (empty($fastest_method['time'])) ? 0 : ($method['time'] / $fastest_method['time'] * 100);
- $method['percent']['fastest']['memory'] = (empty($fastest_method['memory'])) ? 0 : ($method['memory'] / $fastest_method['memory'] * 100);
- $method['percent']['slowest']['time'] = (empty($slowest_method['time'])) ? 0 : ($method['time'] / $slowest_method['time'] * 100);
- $method['percent']['slowest']['memory'] = (empty($slowest_method['memory'])) ? 0 : ($method['memory'] / $slowest_method['memory'] * 100);
- // Assign a grade for time and memory to each method
- $method['grade']['time'] = $this->_grade($method['percent']['fastest']['time']);
- $method['grade']['memory'] = $this->_grade($method['percent']['fastest']['memory']);
- // Percentage calculations for subjects
- foreach ($method['subjects'] as & $subject)
- {
- // Calculate percentage difference relative to fastest and slowest subjects for this method
- $subject['percent']['fastest']['time'] = (empty($fastest_subject['time'])) ? 0 : ($subject['time'] / $fastest_subject['time'] * 100);
- $subject['percent']['fastest']['memory'] = (empty($fastest_subject['memory'])) ? 0 : ($subject['memory'] / $fastest_subject['memory'] * 100);
- $subject['percent']['slowest']['time'] = (empty($slowest_subject['time'])) ? 0 : ($subject['time'] / $slowest_subject['time'] * 100);
- $subject['percent']['slowest']['memory'] = (empty($slowest_subject['memory'])) ? 0 : ($subject['memory'] / $slowest_subject['memory'] * 100);
- // Assign a grade letter for time and memory to each subject
- $subject['grade']['time'] = $this->_grade($subject['percent']['fastest']['time']);
- $subject['grade']['memory'] = $this->_grade($subject['percent']['fastest']['memory']);
- }
- }
- return $codebench;
- }
- /**
- * Callback for array_filter().
- * Filters out all methods not to benchmark.
- *
- * @param string method name
- * @return boolean
- */
- protected function _method_filter($method)
- {
- // Only benchmark methods with the "bench" prefix
- return (substr($method, 0, 5) === 'bench');
- }
- /**
- * Returns the applicable grade letter for a score.
- *
- * @param integer|double score
- * @return string grade letter
- */
- protected function _grade($score)
- {
- foreach ($this->grades as $max => $grade)
- {
- if ($max === 'default')
- continue;
- if ($score <= $max)
- return $grade;
- }
- return $this->grades['default'];
- }
- }
|