Codebench.php 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. <?php
  2. /**
  3. * Codebench — A benchmarking module.
  4. *
  5. * @package Kohana/Codebench
  6. * @category Base
  7. * @author Kohana Team
  8. * @copyright (c) Kohana Team
  9. * @license https://koseven.ga/LICENSE.md
  10. */
  11. abstract class Kohana_Codebench {
  12. /**
  13. * @var string Some optional explanatory comments about the benchmark file.
  14. * HTML allowed. URLs will be converted to links automatically.
  15. */
  16. public $description = '';
  17. /**
  18. * @var integer How many times to execute each method per subject.
  19. */
  20. public $loops = 1000;
  21. /**
  22. * @var array The subjects to supply iteratively to your benchmark methods.
  23. */
  24. public $subjects = [];
  25. /**
  26. * @var array Grade letters with their maximum scores. Used to color the graphs.
  27. */
  28. public $grades = [
  29. 125 => 'A',
  30. 150 => 'B',
  31. 200 => 'C',
  32. 300 => 'D',
  33. 500 => 'E',
  34. 'default' => 'F',
  35. ];
  36. /**
  37. * Constructor.
  38. *
  39. * @return void
  40. */
  41. public function __construct()
  42. {
  43. // Set the maximum execution time
  44. set_time_limit(Kohana::$config->load('codebench')->max_execution_time);
  45. }
  46. /**
  47. * Runs Codebench on the extending class.
  48. *
  49. * @return array benchmark output
  50. */
  51. public function run()
  52. {
  53. // Array of all methods to loop over
  54. $methods = array_filter(get_class_methods($this), [$this, '_method_filter']);
  55. // Make sure the benchmark runs at least once,
  56. // also if no subject data has been provided.
  57. if (empty($this->subjects))
  58. {
  59. $this->subjects = ['NULL' => NULL];
  60. }
  61. // Initialize benchmark output
  62. $codebench = [
  63. 'class' => get_class($this),
  64. 'description' => $this->description,
  65. 'loops' => [
  66. 'base' => (int) $this->loops,
  67. 'total' => (int) $this->loops * count($this->subjects) * count($methods),
  68. ],
  69. 'subjects' => $this->subjects,
  70. 'benchmarks' => [],
  71. ];
  72. // Benchmark each method
  73. foreach ($methods as $method)
  74. {
  75. // Initialize benchmark output for this method
  76. $codebench['benchmarks'][$method] = ['time' => 0, 'memory' => 0];
  77. // Using Reflection because simply calling $this->$method($subject) in the loop below
  78. // results in buggy benchmark times correlating to the length of the method name.
  79. $reflection = new ReflectionMethod(get_class($this), $method);
  80. // Benchmark each subject on each method
  81. foreach ($this->subjects as $subject_key => $subject)
  82. {
  83. // Prerun each method/subject combo before the actual benchmark loop.
  84. // This way relatively expensive initial processes won't be benchmarked, e.g. autoloading.
  85. // At the same time we capture the return here so we don't have to do that in the loop anymore.
  86. $return = $reflection->invoke($this, $subject);
  87. // Start the timer for one subject
  88. $token = Profiler::start('codebench', $method.$subject_key);
  89. // The heavy work
  90. for ($i = 0; $i < $this->loops; ++$i)
  91. {
  92. $reflection->invoke($this, $subject);
  93. }
  94. // Stop and read the timer
  95. $benchmark = Profiler::total($token);
  96. // Benchmark output specific to the current method and subject
  97. $codebench['benchmarks'][$method]['subjects'][$subject_key] = [
  98. 'return' => $return,
  99. 'time' => $benchmark[0],
  100. 'memory' => $benchmark[1],
  101. ];
  102. // Update method totals
  103. $codebench['benchmarks'][$method]['time'] += $benchmark[0];
  104. $codebench['benchmarks'][$method]['memory'] += $benchmark[1];
  105. }
  106. }
  107. // Initialize the fastest and slowest benchmarks for both methods and subjects, time and memory,
  108. // these values will be overwritten using min() and max() later on.
  109. // The 999999999 values look like a hack, I know, but they work,
  110. // unless your method runs for more than 31 years or consumes over 1GB of memory.
  111. $fastest_method = $fastest_subject = ['time' => 999999999, 'memory' => 999999999];
  112. $slowest_method = $slowest_subject = ['time' => 0, 'memory' => 0];
  113. // Find the fastest and slowest benchmarks, needed for the percentage calculations
  114. foreach ($methods as $method)
  115. {
  116. // Update the fastest and slowest method benchmarks
  117. $fastest_method['time'] = min($fastest_method['time'], $codebench['benchmarks'][$method]['time']);
  118. $fastest_method['memory'] = min($fastest_method['memory'], $codebench['benchmarks'][$method]['memory']);
  119. $slowest_method['time'] = max($slowest_method['time'], $codebench['benchmarks'][$method]['time']);
  120. $slowest_method['memory'] = max($slowest_method['memory'], $codebench['benchmarks'][$method]['memory']);
  121. foreach ($this->subjects as $subject_key => $subject)
  122. {
  123. // Update the fastest and slowest subject benchmarks
  124. $fastest_subject['time'] = min($fastest_subject['time'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['time']);
  125. $fastest_subject['memory'] = min($fastest_subject['memory'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['memory']);
  126. $slowest_subject['time'] = max($slowest_subject['time'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['time']);
  127. $slowest_subject['memory'] = max($slowest_subject['memory'], $codebench['benchmarks'][$method]['subjects'][$subject_key]['memory']);
  128. }
  129. }
  130. // Percentage calculations for methods
  131. foreach ($codebench['benchmarks'] as & $method)
  132. {
  133. // Calculate percentage difference relative to fastest and slowest methods
  134. $method['percent']['fastest']['time'] = (empty($fastest_method['time'])) ? 0 : ($method['time'] / $fastest_method['time'] * 100);
  135. $method['percent']['fastest']['memory'] = (empty($fastest_method['memory'])) ? 0 : ($method['memory'] / $fastest_method['memory'] * 100);
  136. $method['percent']['slowest']['time'] = (empty($slowest_method['time'])) ? 0 : ($method['time'] / $slowest_method['time'] * 100);
  137. $method['percent']['slowest']['memory'] = (empty($slowest_method['memory'])) ? 0 : ($method['memory'] / $slowest_method['memory'] * 100);
  138. // Assign a grade for time and memory to each method
  139. $method['grade']['time'] = $this->_grade($method['percent']['fastest']['time']);
  140. $method['grade']['memory'] = $this->_grade($method['percent']['fastest']['memory']);
  141. // Percentage calculations for subjects
  142. foreach ($method['subjects'] as & $subject)
  143. {
  144. // Calculate percentage difference relative to fastest and slowest subjects for this method
  145. $subject['percent']['fastest']['time'] = (empty($fastest_subject['time'])) ? 0 : ($subject['time'] / $fastest_subject['time'] * 100);
  146. $subject['percent']['fastest']['memory'] = (empty($fastest_subject['memory'])) ? 0 : ($subject['memory'] / $fastest_subject['memory'] * 100);
  147. $subject['percent']['slowest']['time'] = (empty($slowest_subject['time'])) ? 0 : ($subject['time'] / $slowest_subject['time'] * 100);
  148. $subject['percent']['slowest']['memory'] = (empty($slowest_subject['memory'])) ? 0 : ($subject['memory'] / $slowest_subject['memory'] * 100);
  149. // Assign a grade letter for time and memory to each subject
  150. $subject['grade']['time'] = $this->_grade($subject['percent']['fastest']['time']);
  151. $subject['grade']['memory'] = $this->_grade($subject['percent']['fastest']['memory']);
  152. }
  153. }
  154. return $codebench;
  155. }
  156. /**
  157. * Callback for array_filter().
  158. * Filters out all methods not to benchmark.
  159. *
  160. * @param string method name
  161. * @return boolean
  162. */
  163. protected function _method_filter($method)
  164. {
  165. // Only benchmark methods with the "bench" prefix
  166. return (substr($method, 0, 5) === 'bench');
  167. }
  168. /**
  169. * Returns the applicable grade letter for a score.
  170. *
  171. * @param integer|double score
  172. * @return string grade letter
  173. */
  174. protected function _grade($score)
  175. {
  176. foreach ($this->grades as $max => $grade)
  177. {
  178. if ($max === 'default')
  179. continue;
  180. if ($score <= $max)
  181. return $grade;
  182. }
  183. return $this->grades['default'];
  184. }
  185. }