There are numerous ways to evaluate the performance of a PHP script. The simplest, our good friend
microtime, allows you to do targeted benchmarking of certain sections of code:
$startTime = microtime(true); functionCall(); $timeDiff = microtime(true) - $startTime; echo 'functionCall() took '. $timeDiff .' seconds';
This works extremely well for quickly testing the performance of a specific piece of code. However, this approach has a few drawbacks:
- Lack of granularity – You only get one number, which is the total amount of time taken between the start and stop points. It reveals very little about what the code is doing that causes it to take that much time.
- Invasiveness – In order to instrument your application, you have to change the code by inserting timing statements in all the relevant places. With a large application and little knowledge about the location of the code causing performance issues, this can clutter your codebase.
- Verbosity – This approach requires at least two lines of code for every section of the app that you want to time.
Thankfully, there are helper classes for this, like
PEAR_Benchmark. With this class, you can easily set marks at critical points of your application and get finer-grained reporting on runtime results. As you can see in the supplied example,
PEAR_Benchmark is simple to use. However, it still requires that you edit your code to set the timing points. This might need dozens of individual marks inserted, only to (assumedly) be removed before the code is pushed to production. Alas…
But fear not, dear reader, for there is a better way! And it is called profiling.
The Joy of Profiling
What does profiling buy you? How about:
- Total request time
- Time spent in every function that was hit during the request (either in absolute time or as a percentage of the request)
- Number of times each function was called over the course of the request
- Rainbows bursting forth from your monitor!
Okay, maybe not exactly rainbows…but look at this colorful chart! It represents the percentage of the request time spent in each function as a portion of the total graph area:
This chart makes it plainly obvious where your program is spending its time. And the best part? Profiling requires absolutely no code modification! That’s right, no more timing statements sprinkled liberally throughout the code to figure out where the slowdown is. So how do you get all this juicy informational goodness?
It’s Not Just A Debugger
Enter Xdebug, a PHP extension that allows you to (among several other very useful things) generate profiling reports on your code. Xdebug is a PECL extension, which means that installing it is easy as pie (note: I am assuming a *nix system here). Just run the following on the command line:
> pecl install xdebug
This should download and install the extension for you. From here, just configure the following settings in your php.ini file:
- Use the Xdebug extension:
Put the following line into your php.ini file (changing out the “/your/particular/path/to/” section with the location of your xdebug.so extension):
- Turning on the profiler:
To optionally generate a profiler report, put the following into your php.ini after the line to include Xdebug:
xdebug.profiler_enable_trigger = 1
You can now trigger the profiler for an individual script run. For web requests, you can turn the profiler on by passing in the query parameter
XDEBUG_PROFILE=1. For example,
http://www.example.com/testScript.php?XDEBUG_PROFILE=1would create a profile for the testScript.php file.
To generate a profiler report for a script running on the CLI, set the environment variable XDEBUG_CONFIG to the value “profiler_enable=1”. For convenience (and because I tend to forget the exact format required), I set up the following shell alias:
alias phpx='XDEBUG_CONFIG="profiler_enable=1" php'
Now I just run
phpx <script name>and Xdebug will create a profiler report automatically. How convenient!
You also have the option to turn on profiling for every single PHP script execution. I generally recommend against this, as profiler report files can be extremely large (on the order of gigabytes) and generating many of them can fill up your disk in short order. That said, to profile on every script execution, use the following instead of
xdebug.profiler_enable = 1
- Set up a location for your profiler reports:
You can tell Xdebug where it should put the reports it generates. The default is
/tmp—I recommend that you put it somewhere with a few gigabytes free, just in case.
xdebug.profiler_output_dir = /tmp
- Change the naming convention for the reports:
The name of the files generated by Xdebug is created automatically based on the
xdebug.profiler_output_namestring, which allows some variables. The ones I find to be most interesting are:
- %s = script path (_home_httpd_html_test_xdebug_test_php)
- %u = timestamp (microseconds)…format: 1179434749_642382
- %p = pid
Xdebug ships with a default of
cachegrind.out.%p, which I don’t really like. I use the following instead:
xdebug.profiler_output_name = cachegrind.out.%s.%u
Once you have your settings arranged to your liking, give it a go and have a look at the report it generates. Sure is a lot of text in there, huh? Now, obviously, this isn’t particularly useful on its own. You need to install a program that can read these files and visualize the data for you. A few options:
- KCachegrind – A free KDE application that provides, in addition to the standard performance data, interesting visualizations of how much time each function took relative to the overall script run time.
- Webgrind – A free web-based report analyzer. Webgrind is fairly simple to set up and runs on any OS.
- MacCallGrind – A commercial, Mac-native application.
- WinCacheGrind – A free Windows application.
Now you’re all set! You can both generate profiler reports and read them. Enjoy!