Code Snippetsprofile.class.php Syndicate content

Tue, 06/24/2008 - 18:29

Every now and then I want to profile a given part of PHP code. For example I want to quickly check wether my current changeset to GeSHi works faster or is horribly slower. For a big change I’ll stick to Xdebug and KCachegrind. But for a quick overview? Overkill in my eyes.

Say hello to profile.class.php, a simple timer class for PHP5 which you can use to get an overview about where how much time is spent. This is in no way a scientific method nor should you take the results of a single run as a basis for you decisions.

I’ve set an emphasize on an easy API so you don’t have to pollute your code with arbitrary hoops and whistles.

UPDATE: You can find the current updated source in the SVN repo of GeSHi.

Simple example

This is a quick example on how you could use the class:

  1. <?php
  2. require 'profile.class.php'; // should be obvious ;-)
  3.  
  4. // here might be uninteresting code
  5.  
  6. profile::start('overall'); // start a timer and give it a name
  7. // we add this one to get a time
  8. // for the overall runtime
  9.  
  10. // this is the code you want to profile
  11. profile::start('$_SERVER');
  12. $foo = count($_SERVER);
  13. profile::stop(); // stop the last active counter
  14.  
  15. profile::start('$GLOBALS');
  16. $bar = count($GLOBALS);
  17. profile::stop();
  18.  
  19.  
  20. profile::stop(); // stop overall timer
  21. profile::print_results(profile::flush()); // print the results

The output will be something like this:

  1. profile results
  2. --------------------------------------
  3. $GLOBALS 0.000027s 100.00%
  4. $_SERVER 0.000035s 128.07%
  5. overall 0.000212s 779.82%

Quite simple with a simple overview as a result.

Codeblocks

If you want to let some implementations of the same thing compete against each other do decide which one is the fastest, I’ve also added the codeblocks method. In the example below you can see one of my testcase which checks wether stristr or preg_match is faster to check wether a string is inside the other on a case insensitive base:

  1. <?php
  2. require 'profile.class.php';
  3.  
  4. $string = file_get_contents('dummy_text.txt');
  5. $iterations = strlen($string);
  6.  
  7. profile::codeblocks(array(
  8. 'stristr' => 'if (stristr($string, "loRem")) {}',
  9. 'preg_match' => 'if (preg_match("/loRem/i", $string)) {}',
  10. ), array(
  11. 'string' => $string,
  12. ), $iterations);
  13.  
  14. profile::print_results(profile::flush());

The results for a dummy text of about 24k size:

  1. profile results
  2. ----------------------------------------
  3. preg_match 0.229575s 100.00%
  4. stristr 2.601574s 1133.21%

So I’d say stristr is pretty slow!

The class

You can also download the file below.

  1. <?php
  2. /**
  3.  * profile.class.php - A timer class for qualitative profiling
  4.  *
  5.  * This is a static class which you can use to qualitatively compare and
  6.  * profile parts of your PHP code. It is as simple as
  7.  *
  8.  * <?php ...
  9.  * require 'profile.class.php';
  10.  * ...
  11.  * profile::start('some name');
  12.  * ...
  13.  * profile::stop();
  14.  * ...
  15.  * profile::print_results(profile::flush());
  16.  * ?>
  17.  *
  18.  * The class itself should be self explaining and well documented. Take a look below!
  19.  *
  20.  *
  21.  *
  22.  * profile.class.php is free software; you can redistribute it and/or modify
  23.  * it under the terms of the GNU General Public License as published by
  24.  * the Free Software Foundation; either version 2 of the License, or
  25.  * (at your option) any later version.
  26.  *
  27.  * profile.class.php is distributed in the hope that it will be useful,
  28.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  29.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  30.  * GNU General Public License for more details.
  31.  *
  32.  * You should have received a copy of the GNU General Public License
  33.  * along with profile.class.php; if not, write to the Free Software
  34.  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  35.  *
  36.  * @author Milian Wolff <mail@milianw.de>
  37.  * @copyright (C) 2008 Milian Wolff
  38.  */
  39.  
  40. class profile {
  41. // list of start and end timestamps
  42. static private $start = array();
  43. static private $end = array();
  44. // current names
  45. static private $running_profiles = array();
  46. static private $cur_name;
  47. /**
  48.   * start the profile timer
  49.   *
  50.   * @param $name optional name for this profile
  51.   */
  52. static public function start($name = 0) {
  53. if (!is_null(self::$cur_name)) {
  54. // nested timer
  55. // add old name to running profiles
  56. array_push(self::$running_profiles, self::$cur_name);
  57. if (self::$cur_name === $name) {
  58. if (is_int($name)) {
  59. $name++;
  60. } else {
  61. $name .= '-SUB';
  62. }
  63. }
  64. }
  65. if (!isset(self::$start[$name])) {
  66. // init
  67. self::$start[$name] = array();
  68. self::$end[$name] = array();
  69. }
  70. self::$cur_name = $name;
  71. // start timer
  72. array_push(self::$start[$name], microtime(true));
  73. }
  74. /**
  75.   * stop the profile timer
  76.   */
  77. static public function stop() {
  78. // stop timer
  79. array_push(self::$end[self::$cur_name], microtime(true));
  80. if (!empty(self::$running_profiles)) {
  81. // got a parent timer (nested timer)
  82. self::$cur_name = array_pop(self::$running_profiles);
  83. } else {
  84. self::$cur_name = null;
  85. }
  86. }
  87. /**
  88.   * get the results and reset internal result cache
  89.   *
  90.   * @param $reverse boolean; calculate deviation to longest diff, defaults to false (shortest diff)
  91.   * @return array of results
  92.   */
  93. static public function flush($reverse = false) {
  94. if (!is_null(self::$cur_name)) {
  95. trigger_error('A timer is still running. Stop it before flushing the results by calling profile::stop().',
  96. E_USER_ERROR);
  97. return;
  98. }
  99. if (empty(self::$start)) {
  100. return array();
  101. }
  102.  
  103. $results = array();
  104. // reset vars
  105. $start = self::$start;
  106. $end = self::$end;
  107. self::$start = array();
  108. self::$end = array();
  109. self::$cur_name = null;
  110.  
  111. $results = array();
  112. $diffs = array();
  113.  
  114. // get runtimes
  115. $names = array_keys($start);
  116. $deviate_key = $names[0];
  117.  
  118. foreach ($names as $key) {
  119. $diffs[$key] = array_sum($end[$key]) - array_sum($start[$key]);
  120. if (($reverse && $diffs[$key] > $diffs[$deviate_key])
  121. || (!$reverse && $diffs[$key] < $diffs[$deviate_key])) {
  122. // remember which run we take as reference point to calculate deviations
  123. $deviate_key = $key;
  124. }
  125. }
  126.  
  127. if ($reverse) {
  128. arsort($diffs);
  129. } else {
  130. asort($diffs);
  131. }
  132.  
  133. // calculate percental deviations and build up return array
  134. foreach ($diffs as $name => $diff) {
  135. array_push($results, array(
  136. 'diff' => $diff,
  137. 'start' => $start[$name],
  138. 'end' => $end[$name],
  139. 'name' => $name,
  140. 'deviation' => ($diffs[$name] / $diffs[$deviate_key])*100,
  141. ));
  142. }
  143.  
  144. return $results;
  145. }
  146. /**
  147.   * default implementation as to how one could present the results, optimized for CLI usage
  148.   *
  149.   * @param $results an array as returned by profile::flush()
  150.   * @param $dont_print optionally; only return the result and dont print it
  151.   * @return string
  152.   */
  153. static public function print_results($results, $dont_print = false) {
  154. $output = "profile results\n";
  155. if (empty($results)) {
  156. $output = "no code was profiled, empty resultset\n";
  157. } else {
  158. // get maximum col-width:
  159. $max_col_width = 0;
  160. for ($key = 0, $n = count($results); $key < $n; ++$key) {
  161. $max_col_width = max($max_col_width, strlen($results[$key]['name']));
  162. }
  163. $output .= str_repeat('-', $max_col_width + strlen(' 0.000000s 100.00%')) ."\n";
  164.  
  165. foreach ($results as $profile) {
  166. $output .= sprintf("%-". $max_col_width ."s %10Fs %10.2F%%\n",
  167. $profile['name'], $profile['diff'], $profile['deviation']);
  168. }
  169. }
  170. if (!$dont_print) {
  171. echo $output;
  172. }
  173. return $output;
  174. }
  175. /**
  176.   * define code blocks and run them in random order, profiling each
  177.   * this is great when writing little scripts to see which implementation of a given feature is faster
  178.   *
  179.   * @param $code_blocks an array with strings of php-code (just like eval or create_function accepts).
  180.   * don't forget keys to give those codeblocks a name
  181.   * @param $vars assoc array with global values which are used in the code blocks (i.e. varname => value)
  182.   * @param $iterations number of times each block gets run
  183.   * @return void
  184.   */
  185. static public function codeblocks($code_blocks, $vars = array(), $iterations = 200) {
  186. if (!empty($vars)) {
  187. $vars_keys = '$'. implode(', $', array_keys($vars));
  188. $vars_values = array_values($vars);
  189. } else {
  190. $vars_keys = '';
  191. $vars_values = array();
  192. }
  193. $blocks = array();
  194. // pita to get random order
  195. foreach ($code_blocks as $name => $block) {
  196. $block = trim($block);
  197. if ($block[strlen($block) - 1] != ';') {
  198. $block .= ';';
  199. }
  200. array_push($blocks, array('name' => $name, 'code' => $block));
  201. }
  202. unset($code_blocks);
  203. shuffle($blocks);
  204. foreach ($blocks as $block) {
  205. $func = create_function($vars_keys, $block['code']);
  206. self::start($block['name']);
  207. for ($i = 0; $i < $iterations; ++$i) {
  208. call_user_func_array($func, $vars_values);
  209. }
  210. self::stop();
  211. }
  212. }
  213. }
AttachmentSize
profile.class_.php.txt6.55 KB

Comments

Post new comment

  • You can use Markdown syntax to format and style the text. Also see Markdown Extra for tables, footnotes, and more.
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <pre>.
  • Web page addresses and e-mail addresses turn into links automatically.

More information about formatting options