profile.class.php
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:
<?php
require 'profile.class.php'; // should be obvious ;-)
// here might be uninteresting code
profile::start('overall'); // start a timer and give it a name
// we add this one to get a time
// for the overall runtime
// this is the code you want to profile
profile::start('$_SERVER');
$foo = count($_SERVER);
profile::stop(); // stop the last active counter
profile::start('$GLOBALS');
$bar = count($GLOBALS);
profile::stop();
profile::stop(); // stop overall timer
profile::print_results(profile::flush()); // print the results
The output will be something like this:
profile results
--------------------------------------
$GLOBALS 0.000027s 100.00%
$_SERVER 0.000035s 128.07%
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:
<?php
require 'profile.class.php';
$string = file_get_contents('dummy_text.txt');
$iterations = strlen($string);
profile::codeblocks(array(
'stristr' => 'if (stristr($string, "loRem")) {}',
'preg_match' => 'if (preg_match("/loRem/i", $string)) {}',
), array(
'string' => $string,
), $iterations);
profile::print_results(profile::flush());
The results for a dummy text of about 24k size:
profile results
----------------------------------------
preg_match 0.229575s 100.00%
stristr 2.601574s 1133.21%
So I’d say stristr
is pretty slow!
The class
You can also download the file below.
<?php
/**
* profile.class.php - A timer class for qualitative profiling
*
* This is a static class which you can use to qualitatively compare and
* profile parts of your PHP code. It is as simple as
*
* <?php ...
* require 'profile.class.php';
* ...
* profile::start('some name');
* ...
* profile::stop();
* ...
* profile::print_results(profile::flush());
* ?>
*
* The class itself should be self explaining and well documented. Take a look below!
*
*
*
* profile.class.php is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* profile.class.php is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with profile.class.php; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* @author Milian Wolff <mail@milianw.de>
* @copyright (C) 2008 Milian Wolff
* @license http://gnu.org/copyleft/gpl.html GNU GPL
*/
class profile {
// list of start and end timestamps
static private $start = array();
static private $end = array();
// current names
static private $running_profiles = array();
static private $cur_name;
/**
* start the profile timer
*
* @param $name optional name for this profile
*/
static public function start($name = 0) {
if (!is_null(self::$cur_name)) {
// nested timer
// add old name to running profiles
array_push(self::$running_profiles, self::$cur_name);
if (self::$cur_name === $name) {
if (is_int($name)) {
$name++;
} else {
$name .= '-SUB';
}
}
}
if (!isset(self::$start[$name])) {
// init
self::$start[$name] = array();
self::$end[$name] = array();
}
self::$cur_name = $name;
// start timer
array_push(self::$start[$name], microtime(true));
}
/**
* stop the profile timer
*/
static public function stop() {
// stop timer
array_push(self::$end[self::$cur_name], microtime(true));
if (!empty(self::$running_profiles)) {
// got a parent timer (nested timer)
self::$cur_name = array_pop(self::$running_profiles);
} else {
self::$cur_name = null;
}
}
/**
* get the results and reset internal result cache
*
* @param $reverse boolean; calculate deviation to longest diff, defaults to false (shortest diff)
* @return array of results
*/
static public function flush($reverse = false) {
if (!is_null(self::$cur_name)) {
trigger_error('A timer is still running. Stop it before flushing the results by calling profile::stop().',
E_USER_ERROR);
return;
}
if (empty(self::$start)) {
return array();
}
$results = array();
// reset vars
$start = self::$start;
$end = self::$end;
self::$start = array();
self::$end = array();
self::$cur_name = null;
$results = array();
$diffs = array();
// get runtimes
$names = array_keys($start);
$deviate_key = $names[0];
foreach ($names as $key) {
$diffs[$key] = array_sum($end[$key]) - array_sum($start[$key]);
if (($reverse && $diffs[$key] > $diffs[$deviate_key])
|| (!$reverse && $diffs[$key] < $diffs[$deviate_key])) {
// remember which run we take as reference point to calculate deviations
$deviate_key = $key;
}
}
if ($reverse) {
arsort($diffs);
} else {
asort($diffs);
}
// calculate percental deviations and build up return array
foreach ($diffs as $name => $diff) {
array_push($results, array(
'diff' => $diff,
'start' => $start[$name],
'end' => $end[$name],
'name' => $name,
'deviation' => ($diffs[$name] / $diffs[$deviate_key])*100,
));
}
return $results;
}
/**
* default implementation as to how one could present the results, optimized for CLI usage
*
* @param $results an array as returned by profile::flush()
* @param $dont_print optionally; only return the result and dont print it
* @return string
*/
static public function print_results($results, $dont_print = false) {
$output = "profile results\n";
if (empty($results)) {
$output = "no code was profiled, empty resultset\n";
} else {
// get maximum col-width:
$max_col_width = 0;
for ($key = 0, $n = count($results); $key < $n; ++$key) {
$max_col_width = max($max_col_width, strlen($results[$key]['name']));
}
$output .= str_repeat('-', $max_col_width + strlen(' 0.000000s 100.00%')) ."\n";
foreach ($results as $profile) {
$output .= sprintf("%-". $max_col_width ."s %10Fs %10.2F%%\n",
$profile['name'], $profile['diff'], $profile['deviation']);
}
}
if (!$dont_print) {
echo $output;
}
return $output;
}
/**
* define code blocks and run them in random order, profiling each
* this is great when writing little scripts to see which implementation of a given feature is faster
*
* @param $code_blocks an array with strings of php-code (just like eval or create_function accepts).
* don't forget keys to give those codeblocks a name
* @param $vars assoc array with global values which are used in the code blocks (i.e. varname => value)
* @param $iterations number of times each block gets run
* @return void
*/
static public function codeblocks($code_blocks, $vars = array(), $iterations = 200) {
if (!empty($vars)) {
$vars_keys = '$'. implode(', $', array_keys($vars));
$vars_values = array_values($vars);
} else {
$vars_keys = '';
$vars_values = array();
}
$blocks = array();
// pita to get random order
foreach ($code_blocks as $name => $block) {
$block = trim($block);
if ($block[strlen($block) - 1] != ';') {
$block .= ';';
}
array_push($blocks, array('name' => $name, 'code' => $block));
}
unset($code_blocks);
shuffle($blocks);
foreach ($blocks as $block) {
$func = create_function($vars_keys, $block['code']);
self::start($block['name']);
for ($i = 0; $i < $iterations; ++$i) {
call_user_func_array($func, $vars_values);
}
self::stop();
}
}
}
Attachment | Size |
---|---|
profile.class_.php.txt | 6.55 KB |
Comments
Want to comment? Send me an email!