Calculate script execution time (PHP class)
Sometimes when writing PHP code that is supposed to run fast we use the script execution calculation functions. Usually it involves (at least for me :) copying the code from example #1 on PHP's microtime function page and pasting it into your script only to do the same all over again the next time.
However, as good as that script is, it is not versatile enough. It does not, for example, allow you to time execution of only some lines or even more - some separated lines.
Let's look at an example:
- for ($i = 0; $i < 100; $i++) {
- fx();
- fy();
- fz();
- }
What if we needed to time the execution of fy()? It's not impossible to do using the usual measures however it becomes a bit tedious. We would have to have multiple microtime calls, some local variable to store the total time and so on.
Therefore, to make my and hopefully someone else's life easier, I wrote a class for those occasions. It allows you to start, stop, pause and resume timing of specific parts of your code. It would even allow you to calculate total time it takes to run fx() AND fz() together.
Here is the full source code of the class:
- <?php
- class Timer
- {
- // command constants
- const CMD_START = 'start';
- const CMD_STOP = 'end';
- // return format constants
- const SECONDS = 0;
- const MILLISECONDS = 1;
- const MICROSECONDS = 2;
- // number of microseconds in a second
- const USECDIV = 1000000;
- /**
- * Stores current state of the timer
- *
- * @var boolean
- */
- private static $_running = false;
- /**
- * Contains the queue of times
- *
- * @var array
- */
- private static $_queue = array();
- /**
- * Start the timer
- *
- * @return void
- */
- public static function start()
- {
- // push current time
- self::_pushTime(self::CMD_START);
- }
- /**
- * Stop the timer
- *
- * @return void
- */
- public static function stop()
- {
- // push current time
- self::_pushTime(self::CMD_STOP);
- }
- /**
- * Reset contents of the queue
- *
- * @return void
- */
- public static function reset()
- {
- // reset the queue
- self::$_queue = array();
- }
- /**
- * Add a time entry to the queue
- *
- * @param string $cmd Command to push
- * @return void
- */
- private static function _pushTime($cmd)
- {
- // capture the time as early in the function as possible
- $mt = microtime();
- // set current running state depending on the command
- if ($cmd == self::CMD_START) {
- // check if the timer has already been started
- if (self::$_running === true) {
- trigger_error('Timer has already been started', E_USER_NOTICE);
- return;
- }
- // set current state
- self::$_running = true;
- } else if ($cmd == self::CMD_STOP) {
- // check if the timer is already stopped
- if (self::$_running === false) {
- trigger_error('Timer has already been stopped/paused or has not yet been started', E_USER_NOTICE);
- return;
- }
- // set current state
- self::$_running = false;
- } else {
- // fail execution of the script
- trigger_error('Invalid command specified', E_USER_ERROR);
- return;
- }
- // recapture the time as close to the end of the function as possible
- if ($cmd === self::CMD_START) {
- $mt = microtime();
- }
- // split the time into components
- list($usec, $sec) = explode(' ', $mt);
- // typecast them to the required types
- $sec = (int) $sec;
- $usec = (float) $usec;
- $usec = (int) ($usec * self::USECDIV);
- // create the array
- $time = array(
- $cmd => array(
- 'sec' => $sec,
- 'usec' => $usec,
- ),
- );
- // add a time entry depending on the command
- if ($cmd == self::CMD_START) {
- array_push(self::$_queue, $time);
- } else if ($cmd == self::CMD_STOP) {
- $count = count(self::$_queue);
- $array =& self::$_queue[$count - 1];
- $array = array_merge($array, $time);
- }
- }
- /**
- * Get time of execution from all queue entries
- *
- * @param int $format Format of the returned data
- * @return int|float
- */
- public static function get($format = self::SECONDS)
- {
- // stop timer if it is still running
- if (self::$_running === true) {
- trigger_error('Forcing timer to stop', E_USER_NOTICE);
- self::stop();
- }
- // reset all values
- $sec = 0;
- $usec = 0;
- // loop through each time entry
- foreach (self::$_queue as $time) {
- // start and end times
- $start = $time[self::CMD_START];
- $end = $time[self::CMD_STOP];
- // calculate difference between start and end seconds
- $sec_diff = $end['sec'] - $start['sec'];
- // if starting and finishing seconds are the same
- if ($sec_diff === 0) {
- // only add the microseconds difference
- $usec += ($end['usec'] - $start['usec']);
- } else {
- // add the difference in seconds (compensate for microseconds)
- $sec += $sec_diff - 1;
- // add the difference time between start and end microseconds
- $usec += (self::USECDIV - $start['usec']) + $end['usec'];
- }
- }
- if ($usec > self::USECDIV) {
- // move the full second microseconds to the seconds' part
- $sec += (int) floor($usec / self::USECDIV);
- // keep only the microseconds that are over the self::USECDIV
- $usec = $usec % self::USECDIV;
- }
- switch ($format) {
- case self::MICROSECONDS:
- return ($sec * self::USECDIV) + $usec;
- case self::MILLISECONDS:
- return ($sec * 1000) + (int) round($usec / 1000, 0);
- case self::SECONDS:
- default:
- return (float) $sec + (float) ($usec / self::USECDIV);
- }
- }
- /**
- * Get the average time of execution from all queue entries
- *
- * @param int $format Format of the returned data
- * @return float
- */
- public static function getAverage($format = self::SECONDS)
- {
- $count = count(self::$_queue);
- $sec = 0;
- $usec = self::get(self::MICROSECONDS);
- if ($usec > self::USECDIV) {
- // move the full second microseconds to the seconds' part
- $sec += (int) floor($usec / self::USECDIV);
- // keep only the microseconds that are over the self::USECDIV
- $usec = $usec % self::USECDIV;
- }
- switch ($format) {
- case self::MICROSECONDS:
- $value = ($sec * self::USECDIV) + $usec;
- return round($value / $count, 2);
- case self::MILLISECONDS:
- $value = ($sec * 1000) + (int) round($usec / 1000, 0);
- return round($value / $count, 2);
- case self::SECONDS:
- default:
- $value = (float) $sec + (float) ($usec / self::USECDIV);
- return round($value / $count, 2);
- }
- }
- }
First, let's look at the public methods that are available to you:
- start() - start/resume the timer
- stop() - stop/pause the timer
- reset() - reset the timer
- get([$format]) - stops the timer (if required) and returns the amount of time as number of seconds, milliseconds or microseconds. The type of the result depends on the value of the $format parameter. The format can be one of the following three: Timer::SECONDS (default), Timer::MILLISECONDS or Timer::MICROSECONDS. if the format is defined as seconds, the result will be a float, otherwise an int.
How it works
The class has a "queue" of times. One Timer::start()/Timer::stop() cycle counts as one entry in the queue with its own execution time. Therefore if you only do start() and stop() once, one queue item will be added. If you call Timer::start() and Timer::stop() multiple times without calling Timer::reset(), multiple entries will get added to the queue.
Then, when you call Timer::get(), the function will go through every queue entry, calculate the time that it took to execute and sum everything up to get the total time.
Whenever you need to restart the calculation just call Timer::reset(). That will clear the queue of current times.
Examples
The first and the most usual scenario, where you want to calculate the time it takes to run your whole script, is the easiest one. Just add Timer::start() at beginning of the file/script and print Timer::get(); at the end.
Next, let's rewrite the examples we mentioned earlier. As you will see it is not much more different than timing the whole script:
- for ($i = 0; $i < 100; $i++) {
- fx();
- // calculate the time it takes to run fy()
- Timer::start();
- fy();
- Timer::stop();
- fz();
- }
- print Timer::get();
- // or
- print Timer::get(Timer::MICROSECONDS);
- // or
- print Timer::get(Timer::MILLISECONDS);
As previously mentioned, if you wanted to calculate the total time it takes to execute fx() AND fz() we would need to adjust the code to wrap two blocks of code instead of just one:
- for ($i = 0; $i < 100; $i++) {
- // calculate the time it takes to run fx()
- Timer::start();
- fx();
- Timer::stop();
- fy();
- // calculate the time it takes to run fz()
- Timer::start();
- fz();
- Timer::stop();
- }
- print Timer::get();
In case you want to track how much time it has taken for the script to run "up to now" you can simply call the Timer::get() function in the middle of the loop:
- for ($i = 0; $i < 100; $i++) {
- fx();
- // calculate the time it takes to run fy()
- Timer::start();
- fy();
- Timer::stop();
- fz();
- print Timer::get();
- }
IMPORTANT!
Timer::get() also stops the timer as it needs to set the final queue entry's end time. Therefore, if you call it between two blocks of code where the time gets calculated, you need to make sure you resume the timer by calling Timer::start().
Non-static version of the class
For those that don't like static classes or who might need multiple instances of the Timer class, here is a modified version of the static class:
- <?php
- class Timer
- {
- // command constants
- const CMD_START = 'start';
- const CMD_STOP = 'end';
- // return format constants
- const SECONDS = 0;
- const MILLISECONDS = 1;
- const MICROSECONDS = 2;
- // number of microseconds in a second
- const USECDIV = 1000000;
- /**
- * Stores current state of the timer
- *
- * @var boolean
- */
- private $_running = false;
- /**
- * Contains the queue of times
- *
- * @var array
- */
- private $_queue = array();
- /**
- * Start the timer
- *
- * @return void
- */
- public function start()
- {
- // push current time
- $this->_pushTime(self::CMD_START);
- }
- /**
- * Stop the timer
- *
- * @return void
- */
- public function stop()
- {
- // push current time
- $this->_pushTime(self::CMD_STOP);
- }
- /**
- * Reset contents of the queue
- *
- * @return void
- */
- public function reset()
- {
- // reset the queue
- $this->_queue = array();
- }
- /**
- * Add a time entry to the queue
- *
- * @param string $cmd Command to push
- * @return void
- */
- private function _pushTime($cmd)
- {
- // capture the time as early in the function as possible
- $mt = microtime();
- // set current running state depending on the command
- if ($cmd == self::CMD_START) {
- // check if the timer has already been started
- if ($this->_running === true) {
- trigger_error('Timer has already been started', E_USER_NOTICE);
- return;
- }
- // set current state
- $this->_running = true;
- } else if ($cmd == self::CMD_STOP) {
- // check if the timer is already stopped
- if ($this->_running === false) {
- trigger_error('Timer has already been stopped/paused or has not yet been started', E_USER_NOTICE);
- return;
- }
- // set current state
- $this->_running = false;
- } else {
- // fail execution of the script
- trigger_error('Invalid command specified', E_USER_ERROR);
- return;
- }
- // recapture the time as close to the end of the function as possible
- if ($cmd === self::CMD_START) {
- $mt = microtime();
- }
- // split the time into components
- list($usec, $sec) = explode(' ', $mt);
- // typecast them to the required types
- $sec = (int) $sec;
- $usec = (float) $usec;
- $usec = (int) ($usec * self::USECDIV);
- // create the array
- $time = array(
- $cmd => array(
- 'sec' => $sec,
- 'usec' => $usec,
- ),
- );
- // add a time entry depending on the command
- if ($cmd == self::CMD_START) {
- array_push($this->_queue, $time);
- } else if ($cmd == self::CMD_STOP) {
- $count = count($this->_queue);
- $array =& $this->_queue[$count - 1];
- $array = array_merge($array, $time);
- }
- }
- /**
- * Get time of execution from all queue entries
- *
- * @param int $format Format of the returned data
- * @return int|float
- */
- public function get($format = self::SECONDS)
- {
- // stop timer if it is still running
- if ($this->_running === true) {
- trigger_error('Forcing timer to stop', E_USER_NOTICE);
- $this->stop();
- }
- // reset all values
- $sec = 0;
- $usec = 0;
- // loop through each time entry
- foreach ($this->_queue as $time) {
- // start and end times
- $start = $time[self::CMD_START];
- $end = $time[self::CMD_STOP];
- // calculate difference between start and end seconds
- $sec_diff = $end['sec'] - $start['sec'];
- // if starting and finishing seconds are the same
- if ($sec_diff === 0) {
- // only add the microseconds difference
- $usec += ($end['usec'] - $start['usec']);
- } else {
- // add the difference in seconds (compensate for microseconds)
- $sec += $sec_diff - 1;
- // add the difference time between start and end microseconds
- $usec += (self::USECDIV - $start['usec']) + $end['usec'];
- }
- }
- if ($usec > self::USECDIV) {
- // move the full second microseconds to the seconds' part
- $sec += (int) floor($usec / self::USECDIV);
- // keep only the microseconds that are over the self::USECDIV
- $usec = $usec % self::USECDIV;
- }
- switch ($format) {
- case self::MICROSECONDS:
- return ($sec * self::USECDIV) + $usec;
- case self::MILLISECONDS:
- return ($sec * 1000) + (int) round($usec / 1000, 0);
- case self::SECONDS:
- default:
- return (float) $sec + (float) ($usec / self::USECDIV);
- }
- }
- /**
- * Get the average time of execution from all queue entries
- *
- * @param int $format Format of the returned data
- * @return float
- */
- public static function getAverage($format = self::SECONDS)
- {
- $count = count($this->_queue);
- $sec = 0;
- $usec = $this->get(self::MICROSECONDS);
- if ($usec > self::USECDIV) {
- // move the full second microseconds to the seconds' part
- $sec += (int) floor($usec / self::USECDIV);
- // keep only the microseconds that are over the self::USECDIV
- $usec = $usec % self::USECDIV;
- }
- switch ($format) {
- case self::MICROSECONDS:
- $value = ($sec * self::USECDIV) + $usec;
- return round($value / $count, 2);
- case self::MILLISECONDS:
- $value = ($sec * 1000) + (int) round($usec / 1000, 0);
- return round($value / $count, 2);
- case self::SECONDS:
- default:
- $value = (float) $sec + (float) ($usec / self::USECDIV);
- return round($value / $count, 2);
- }
- }
- }
The way you would use this class is pretty similar to the static class except you can instantiate it and have multiple timers running on your page:
- // instantiate two copies of the Timer class
- $t1 = new Timer();
- $t2 = new Timer();
- for ($i = 0; $i < 100; $i++) {
- // calculate the time it takes to run fx() using timer #1
- $t1->start();
- fx();
- $t1->stop();
- // calculate the time it takes to run fy() using timer #2
- $t2->start();
- fy();
- $t2->stop();
- }
- // get results
- print $t1->get();
- print $t2->get(Timer::MILLISECONDS);
Hopefully these examples help. If not, please leave your comments with your questions or suggestions.


