Does it take too long for your PHP page to load in the browser? Have you been staring at the code for ages and tried many fixes that didn't help much? Still no idea where the performance bottleneck is? Could it be somewhere in that bloated framework that you use? Or in some new class that your co-worker made last week? Or, heaven forbid, somewhere in your own code? To find out, a profiler will be of tremendous help. In this post, I will show how to use XDebug's profiler and KCacheGrind to help make your code faster.

Prerequisites

I assume you have Apache and PHP already installed on a Linux machine. I will also assume you use a RedHat type of distribution (RHEL, CentOS, Fedora), but that's just because I happen to use such a distribution (CentOS 7 to be precise). With small modifications, the following will probably also work in other distributions.
Maybe you already have XDebug, because it's also a great debugging tool that works very will with many IDEs and with Visual Studio Code. If you don't have it yet, install it:
sudo yum install php-pecl-xdebug
To visualize the profiling results, we will use KCacheGrind:
sudo yum install kdesdk-kcachegrind
Another prerequisite is that you have enough disk space, because the profiling data written to the filesystem by XDebug can get quite big.

Configuring XDebug profiler

XDebug is a PHP extension, so it is configured in the PHP config file php.ini, or in CentOS 7's case in the file /etc/php.d/xdebug.ini. Edit this file:
sudo vi /etc/php.d/xdebug.ini
Add the following line to enable profiling:
xdebug.profiler_enable = 1
Make sure to remove or comment that line when you're done because it will slow things down and write a lot of data to disk.
To tell XDebug where in the file system it should write its profiling data (e.g. /tmp/), add the following configuration:
xdebug.profiler_output_dir = /tmp/
Check that Apache has write permissions for that directory. Now restart Apache to let it use the new settings:
sudo service httpd restart

Running a profiling session

Now open the slow page in your browser. After it finished loading, check the files in the profiler output directory (/tmp in the example) that start with cachegrind.out. These contain the profiling data for the various Apache processes. The numbers at the end indicate the Apache process ids.

Analyzing the profiling data

You need to find the cachegrind.out file that corresponds to the Apache (httpd) process that served the page you just opened in your browser. Maybe you know the relevant Apache process id. If so, the file you're looking for is cachegrind.out.<id>. If you're not sure which Apache process served your page, just try the file with the biggest size, and if it turns out that it contains data completely unrelated to your session, try the others.
Open the file with KCacheGrind:
kcachegrind /tmp/cachegrind.out.<id>
In KCacheGrind, on the left, you will see a list of functions, ordered by execution time. So the top one takes most time. In the column Self you can see how much of that time is spent directly in that function. The rest of the time is spent in other functions that are called by the function. To see which functions are called and how much time they take, click on the function's row. More details will apear on the right. The tab Callees at the bottom shows the called functions.
A lot more information can be seen in KCacheGrind, so check out its excellent Help documentation. But with just the data discussed above, you can identify and fix many bottlenecks already. If you see that a function has a lot of Self time, have a look at the code in that function and see if anything can be optimized there. If a called function takes a lot of time, see if you can avoid calling that function, or call it less often. Or click on that function to get more details and see if you can optimize that one.

Good luck optimizing your PHP code. If it gets used a lot, you'll save many of us a lot of time.