Friday, June 06, 2008

Profiling PHP with XDebug

Resolving performance problems is hard (even more so when you have to do so with somebody else's code) and some clear measurements are very welcome. I tried out XDebug some years back when and it didn't work very well back then, but the latest release seems quite good. Here is how to use its profiler:

  1. First and foremost make sure that you are not using it on a production server! Good as it may be, it adds a substantial slowdown to the processing and it some rare cases the server could crash!
  2. Find out the PHP version you have and the location of PHP ini. You can do this by viewing a simple php file containing nothing more than:
    <?php
      phpinfo();
    
  3. Get the XDebug module corresponding to your PHP version. This can be as simple as doing sudo apt-get install php-xdebug or going to the XDebug site and downloading it
  4. Register XDebug as a Zend extension (it is very importand not to register it as a normal extensions, that is with php_ext, but as a Zend extension with zend_extension_ts because it needs to hook in at a much deeper level than a normal extension)
  5. Setup the correct configuration in your php.ini. The documentation is a good starting point. Some things you should remember:
    • Make sure that xdebug.profiler_enable is set to 1
    • Make sure that xdebug.profiler_output_dir is set to an existing directory
    • Change the xdebug.profiler_output_name setting, since the default will result almost certainly in data loss (since it saves the profile for the same page in the same file every time). I usually use cachegrind.out.%R.%t.%r
    The parameters can also be set on a per-directory basis if that's what you need using .htaccess files and php_value / php_flag.
  6. The directory containing the trace files can grow very big very quick, so make sure that you keep an eye on it and purge it from time to time. This is an other reason to only use it on a test server.
  7. At this point you have two options: you can use either your web-browser to visit the target URL or a command line program like cURL or wget. The advantages/disadvantages being:
    • With a web-browser you are using a real client which is fetching all the dependencies of the page. Also, you can fetch pages which have complex access methods (like HTTPS with sessions which require login). The disadvantage is that you have no precise measure of time, only impressions (this feels slow), which can mislead you.
    • With a command-line program. These don't fetch all the dependencies and need a little work to set-up to handle authenticated pages (if you need HTTPS I definitely suggest cURL), but can be more easily benchmarked.
  8. Now that you have the trace data, you would load it up into something like KCacheGrind (for Linux) or WCacheGrind (for Windows). Don't let the fact that the later wasn't updated in three years scare you, it still works very well.
  9. At this point you can have two possibilities: a function taking long time to execute or a function being called many, many times. Also, keep in mind that the times displayed from the profiling information are relevant only from a magnitude point of view, not their exact value (again, enabling profiling slows the system down considerably, so disabling it will speed it up). There is no general silver bullet to make scripts fast, however here are two tips to get you started:
    • Know the functions available in PHP and avoid rewriting them, since they are implemented directly in C which is much more efficient. Be especially weary of constructs which loop over arrays for example.
    • If a function is called repeatedly, but it always gives the same result for the same parameters, consider caching the result and first trying a lookup in the cache before starting to compute the answer.

Happy profiling! To give you an idea about the benefits: I used this technique on the blog plugin for DokuWiki and obtained a speedup of ~20%!

 
{"cc_re__cc_re__cc_by_sa_3_0": ["getManager._PopupConfig(document.getElementById(\"Feed1\"));' target='configFeed1' title='Edit'>\n<img alt='' height='18' src='https://resources.blogblog.com/img/icon18_wrench_allbkg.png' width='18'/>\n</a>\n</span>\n</span>\n<div class='clear'></div>\n</div><div class='widget HTML' data-version='1' id='HTML6'>\n<h2 class='title'>License and disclaimer</h2>\n<div class='widget-content'>\n<p>The opinions shared here are my own and do not neccesarilly reflect the opinions of a past or current employer.</p>\n\n<a href=\"http://creativecommons.org/licenses/by-sa/3.0/\" rel=\"license\"><img alt=\"Creative Commons License\" style=\"border-width:0\" src=\"http://i.creativecommons.org/l/by-sa/3.0/88x31.png\"/></a><br/>This <span xmlns:dc=\"http://purl.org/dc/elements/1.1/\" href=\"http://purl.org/dc/dcmitype/Text\" rel=\"dc:type\">work</span> is licensed under a <a href=\"http://creativecommons.org/licenses/by-sa/3.0/\" rel=\"license\">Creative Commons Attribution-Share Alike 3.0 Unported License</a>.<br/>\n</div>\n<div class='clear'></div>\n<span class='widget-item-control'>\n<span class='item-cont", "claimer</h2>\n<div class='widget-content'>\n<p>The opinions shared here are my own and do not neccesarilly reflect the opinions of a past or current employer.</p>\n\n<a href=\"http://creativecommons.org/licenses/by-sa/3.0/\" rel=\"license\"><img alt=\"Creative Commons License\" style=\"border-width:0\" src=\"http://i.creativecommons.org/l/by-sa/3.0/88x31.png\"/></a><br/>This <span xmlns:dc=\"http://purl.org/dc/elements/1.1/\" href=\"http://purl.org/dc/dcmitype/Text\" rel=\"dc:type\">work</span> is licensed under a <a href=\"http://creativecommons.org/licenses/by-sa/3.0/\" rel=\"license\">Creative Commons Attribution-Share Alike 3.0 Unported License</a>.<br/>\n</div>\n<div class='clear'></div>\n<span class='widget-item-control'>\n<span class='item-control blog-admin'>\n<a class='quickedit' href='//www.blogger.com/rearrange?blogID=35005627&widgetType=HTML&widgetId=HTML6&action=editWidget&sectionId=sidebar' onclick='return _WidgetManager._PopupConfig(document.getElementById(\"HTML6\"));' target='configHTML6' title='Edit'>\n<img alt='' height='18' src='https://resources.blogblog.com/img/ico"]}