PHP Performance Tuning
Profiling with a Debugger Extension
Problem
You want a robust solution for profiling your applications so that you can continually monitor where the program spends most of its time.
Solution
Use Xdebug, available from PECL. With Xdebug installed, adding xdebug.profiler_enable=1 to your php.ini configuration dumps a trace file to disk. Parsing that trace file with a tool gives you a breakdown of how time was spent during that run of the PHP script.
Discussion
With Xdebug installed, it’s a simple matter to start a profiling session that will store reporting information on application runtime.
To generate this for all requests, set the xdebug.profiler_enable configuration variable to 1. To conditionally generate this data, set xdebug.profiler_enable to off and xdebug.profiler_enable_trigger to on. In this configuration, Xdebug will only profile when you pass in a GET, POST, or Cookie variable named XDEBUG_PROFILE set to any value.
Though less simple to activate, conditional generation has benefits because you only save logs for selected requests. For complex applications, profiling dumps can be quite large; this prevents you from running out of disk space. Also, though not ideal, in the event you cannot replicate a problem in your testing environment, you can more safely run Xdebug in production.
The output files generated by Xdebug can be stored anywhere you want them, as long as it’s writable by PHP. Set the directory using the xdebug.profiler_output_dir configuration variable and the filename using xdebug.profiler_output_name.
By default, the output filename is cachegrind.out. followed by the process ID. This value makes it difficult to look at the filename and know what was profiled. Fortunately, Xdebug allows you to use a variety of formats. For example, you can use xdebug.profiler_output_name=cachegrind.out.%R.%t to put the request URI and timestamp. This generates filenames like cachegrind.out._factorial_php.1388986739.
Process the output files with an application to more easily view and understand the data. The longest-running functions are good places to start when looking for opportunities to optimize.
A popular tool is KCachegrind, a GUI application used to drill down deeply into applications to determine where hotspots and bottlenecks are occurring. A cross-platform version of this tool, QCachegrind, is available for people not running the KDE Unix desktop environment, such as developers on MacOS X or Windows. QCachegrind requires Qt 4.4 or above.
As a (simplistic) example, this code prints the first 50 factorial numbers:
function factorial($x) {
return ($x == 1) ? 1 : $x * factorial($x - 1);
}
for ($i = 1; $i <= 50; $i++) {
print "$i: " . factorial($i) . "\n";
}
Loading the Xdebug profile dump into QCachegrind, allows you to see that factorial() is called recursively 2,450 times. This takes 13,376 cycles.
Figure Inspecting profiling results in QCachegrind
Looking to reduce the overhead of recursion, you add a quick-and-dirty memoization cache:
function factorial($x) {
static $cache = [];
if (isset($cache[$x])) return $cache[$x];
$cache[$x] = (($x == 1) ? 1 : $x * factorial($x - 1));
return $cache[$x];
}
for ($i = 1; $i <= 50; $i++) {
print "$i: " . factorial($i) . "\n";
}
Now, when you load the profiler output, there are no self-referential calls and there is an order of magnitude decrease in CPU cycles to 643, as shown in figure.
Figure Checking for im111proved results
Depending on your system, QCachegrind can be cumbersome to install because it has a dependency on Qt. Webgrind is an all-PHP application that parses Xdebug output. It’s not as full featured, but it does the job for basic exploration, and it’s simple to install because it’s a PHP script.
Figure Inspecting profiling results in Webgrind
No comments:
Post a Comment