PHP PGO build for maximum performance on Windows

In the last few years VC++ team have done some awesome work with profile guided optimizations (PGO). PGO improves performance of certain code paths which are more likely to be used in production environments. Most of the teams in Microsoft have been using this technology to speed up most commonly used scenarios. One of the ideas initially floated by Garrett was to start using PGO for building PHP. Garrett wrote a blog on how to produce a PGO build but the steps involved were little complex. I started looking into simplifying the process and came up with a simpler way. I am going to walk you through the steps I used in this blog. All the steps written in this blog are tested to work with VC9 compiler. I am assuming you already have a PHP build environment set. Go through this and this blog to setup a PHP build environment if you don’t have one. I am using PHPROOT environment variable to refer to root folder where PHP build environment is set. If you used winbuild.bat to setup build environment, PHPROOT refers to C:\php\php_sdk\php_53_dev\vc9\x86\PHP_5_3.

Note: PGO support is not available in VC++ express edition. To build PGO optimized binaries, you need a version of Visual Studio which supports this feature.
1. Add PGO support to build system
PHP build scripts, as available in PHP source tree, don’t have support for PGO. So first thing you need to do is download this patch to %phproot%\win32\build and apply it on your system. This patch will add support for “–enable-pgi” and “–with-pgo” options in PHP build system. When “–enable-pgi” option is used, PHP build system will add “/LTCG:PGINSTRUMENT” switch to linker command to build instrumented binaries. Option “–with-pgo” will make build system to add “/LTCG:PGOPTIMIZE” switch to linker commands. This switch will make linker use PGO data to produce final optimized binaries. Both “–enable-pgi” and “–with-pgo” cannot be used together. This patch will also make “nmake clean-all” option to delete PGO related files in addition to other cleanup tasks it already does. Additionally you will get an option to delete all files except PGO profile data using “nmake clean-pgo” command (used in step 4 below). After applying the patch, run %phproot%\buildconf.bat to regenerate configure.js again. You can verify this step by running “%phproot%\configure –help” and confirming that output shows “–enable-pgi” and “–with-pgo” options.
2. Build instrumented binaries
PGO build is a two-step process. First step is to collect data identifying most commonly used paths. Second step is to feed this data into build system which then optimizes these paths. Collecting training data requires you to build instrumented binaries which have code to collect profile data. Let’s go through steps to produce these instrumented binaries.
First delete the folder containing non PGO binaries using “rd /s /q %PHPROOT%\release” command. Open %PHPROOT%\config.nice.bat and add switch “–enable-pgi” option to the configure command. Run “config.nice.bat” to create a new makefile and use “nmake snap” to produce a PHP build containing instrumented binaries.

After the build is complete, you will see “.pgd” files created in the %PHPROOT%\release folder along with usual “.exe”, “.dll”, “.pdb” files. Build process uses php.exe to do few steps. So after the build is complete, you will see php!1.pgc and php5!1.pgc in release folder. Delete these files so that profile data doesn’t include code paths which were used during build process. Don’t touch any of the other files yet and move to step 3.

3. Collect training data
To collect training data, pick the build packages (php-*.zip and pecl-*.zip) from %PHPROOT%\Release folder and install it on your web server. For this blog I am training the binaries for wordpress. So I copied and installed instrumented binaries on my web server running wordpress. If your build machine itself has the web application installed, you can do this on the build machine itself. If your web server doesn’t have visual studio installed, it won’t have PGO runtime (pgort90.dll) which is required to run instrumented binaries. Pick this file from you build machine and copy to the folder your web server containing php-cgi.exe.
Now open “%windir%\system32\inetsrv\config\applicationHost.config” (for IIS 7.0 and IIS 7.5) or “%windir%\system32\inetsrv\fcgiext.ini” (for IIS 5.1 and IIS 6.0) and set PHP_FCGI_MAX_REQUESTS equal to number of requests you are going to send to cover most used paths. I changed this setting to 15. Keep “InstanceMaxRequests” setting in FastCGI process pool higher than PHP_FCGI_MAX_REQUESTS value so that PHP terminates itself before FastCGI forcibly kills it. When php-cgi.exe dies on its own, PGO data will be dumped to “.pgc” files. Now you are ready to collect training data.
Send requests which cover code paths you want to optimize the most. In my case, I sent 6 requests to home page and 6 to individual posts to identify these as the most requested parts of my website. I sent 2 requests to category home page and 1 to about page to optimize these paths as well but identified these pages as less hot than home page and individual posts. Choose these requests carefully and only send requests to most frequently visited pages so that this part gets optimized the most. If you cover too much surface area in training data, linker won’t be able optimize as effectively. After PHP_FCGI_MAX_REQUESTS, php-cgi.exe will die and dump “.pgc” files in PHP install root. Find and copy all the “.pgc” files in a folder and then copy to your build machine.
4. Merge training data and build optimized binaries
Copy all the “.pgc” generated in step3 to %PHPROOT%\release folder along with “.pgd” files. Run “nmake clean-pgo” to delete all files except “.pgd” and “.pgc” files. Now change config.nice.bat to use “–with-pgo” instead of “–enable-pgi” and then run config.nice.bat to generate new makefile. Run “nmake snap” to build optimized build and that’s it.

As PHP source code changes are incremental, you don’t need to collect training data for each build. For building latest PHP sources, you can just sync and run “nmake snap” again to use existing training data with latest sources. As source code changes increase, training data will be less accurate and less effective. You can update training data once a month and before the final release to produce useful PGO builds regularly. Run “nmake clean-all” before using new training data so that old training data is deleted.

To build non-PGO build, run “nmake clean-pgo” to delete all binaries (but keeping training data). Remove “–with-pgo” option from config.nice.bat and run it to generate new makefile. Use “nmake snap” to produce non-PGO build.
WordPress Performance Test Results
Let’s go through some quick results I got on my performance bench using PGO optimized build and compare it with non-optimized build from my build machine.

Response Time
Regular Build
726 ms
PGO Optimized
79.89 (17.40% better)
619 ms (14.74% better)
I got approx. 17% better throughput and 15% better response time with PGO build compared to non-PGO build for wordpress. For some other applications, I have seen as much as 30% improvement. If you are trying to get maximum performance output from your windows server, PGO build is something you should definitely consider. If you try out PHP PGO build and run into a problem, sending me email me will get you instant helpJ.