Profiling Drupal code using Xdebug

28 Jun 2009
Posted by doq

PHP owns special Xdebug extension that can be used for debugging and profiling PHP code. Profiling means determining bottlenecks in your scripts and visualize them in some external application (we'll use KCacheGrind through out the page).

In this article we'll give a real example how profiling can help to improve your PHP web-site's performance. We will profile Drupal 6 core.

Installing and configuring Xdebug

You should install php5-xdebug package through your package manager.

The following minimal configuration is required to make Xdebug log profiler results into /tmp/drupal-cachegrind.out file:

# Depending on Linux distribution this can be even more
# simpler, I think:
#   extension = xdebug.so
zend_extension = ${extension_dir}"/xdebug.so"
 
# Enables Xdebug's profiler which creates files in the profile
# output directory. Those files can be read by KCacheGrind to
# visualize your data.
xdebug.profiler_enable = 1
 
# The directory where the profiler output will be written to, make
# sure that the user who the PHP will be running as has write
# permissions to that directory.
xdebug.profiler_output_dir = /tmp
 
# This setting determines the name of the file that is used to dump
# traces into.
xdebug.profiler_output_name = "drupal-cachegrind.out"

Restart Apache service to enable new PHP module and settings:

/etc/rc.d/apache2 restart

Open PHP page in your web-browser and /tmp/drupal-cachegrind.out file will be created. Aware that after you open or reload page the profiler file will be overwritten with new data.

Profiling in KCachegrind

For demonstation I used some real Drupal web-site that works with about 250 various modules installed, has 50K nodes and 1K user accounts. Profiler file was generated for web-site's front page.

Run KCachegrind and open /tmp/drupal-cachegrind.out file. Application will give you a fancy presentation of all your Drupal functions executed during the request and many more information. The most interesting is "Flat Profile" pane showing all functions sorted by time spent in that function. Turn Show Absolute Costs button on if it's not clicked already.

KCachegrind

Description of fields:

Column Description
Incl. Time spent in this function in µs.
Self Time spent in this function (without children) in µs.
Called Number determines how much times specified function was called.
Function Name of function. Internal function names are prefixed with php::
Location File name where function resides.

Clicking on Show Relative Cost button will show you relative times spent in each function in percents.

KCachegrind

For example, module_implements() function implementation takes only 5.46% CPU (42ms) time of all the request. So there might be far better candidates for optimization. Anyway we will try to optimize this function.

I've totally changed some module_implements() implementation by adding some caching into it:

  1. function module_implements($hook, $sort = FALSE, $refresh = FALSE) {
  2. static $implementations;
  3. static $cache_exists;
  4.  
  5. if (!isset($cache_exists)) {
  6. $cache_exists = function_exists('cache_get');
  7. }
  8.  
  9. // Load implementations from cache.
  10. if (!isset($implementations)) {
  11. if ($cache_exists) {
  12. $cache = cache_get('module_implements');
  13. if ($cache) {
  14. $implementations = $cache->data;
  15. }
  16. else {
  17. $implementations = array();
  18. }
  19. }
  20. }
  21.  
  22. if ($refresh) {
  23. if ($cache_exists) {
  24. cache_clear_all('module_implements', 'cache');
  25. }
  26. $implementations = array();
  27. return;
  28. }
  29.  
  30. if (!isset($implementations[$hook])) {
  31. $implementations[$hook] = array();
  32. $list = module_list(FALSE, TRUE, $sort);
  33. foreach ($list as $module) {
  34. if (module_hook($module, $hook)) {
  35. $implementations[$hook][] = $module;
  36. }
  37. }
  38.  
  39. if ($cache_exists) {
  40. cache_set('module_implements', $implementations);
  41. }
  42. }
  43.  
  44. // The explicit cast forces a copy to be made. This is needed because
  45. // $implementations[$hook] is only a reference to an element of
  46. // $implementations and if there are nested foreaches (due to nested node
  47. // API calls, for example), they would both manipulate the same array's
  48. // references, which causes some modules' hooks not to be called.
  49. // See also http://www.zend.com/zend/art/ref-count.php.
  50. return (array)$implementations[$hook];
  51. }

Before doing all the stuff this code checks whether cache_get() function exists because it might be unavailable on update.php page, for example.

One silly problem I see with this implementation is that it calls cache_set() function too often when cache hit missed. But misses will be very rare so that might be not a big problem.

To apply change to module_implements() you may apply this patch.

Here are the benchmarks after patch has been applied. I've pressed Reload in browser two times to make sure I will profile module_implements() function that just gets data from cache and doesn't do any calls to cache_set():

KCachegrind

Results:

Without module_implements patch With module_implements patch applied
42 ms 3 ms

Difference between these times will not be so big when we will turn off Xdebug, so you decide whether to apply this micro-optimization to your Drupal site.

These results are very approximate. Some real ab2 benchmarks should be processed to calculate the mean.

More information

More documentation about profiling code using KCachegrind can be found here.

Another nice article about benchmarking Drupal with Xdebug and KCachegrind.

AttachmentSize
module_implements-caching.patch1.11 KB

Comments

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

More information about formatting options

 
 
 
If you have found mistakes in the text then please select it and press Ctrl-Enter to send report to the site administrator.