
I previously outlined the benefits of using HipHop for PHP over PHP-FPM, specifically with the fact that it is just plain faster. The setup, however, is a little more complicated than traditional PHP, and the chance of finding it built into one of your repositories is rather low.
Thus, I've created this tutorial on how to compile and set up HipHop for PHP on Ubuntu 11.10, since their instructions are a little dated/inaccurate at times. Most of the guide is dervived from here, with additions and fixes from myself.
Step 1: Prerequisites
HipHop requires quite a few packages, most of which should be available in your distribution's repository. On Ubuntu, the process goes something like this:
sudo apt-get install git-core cmake g++ libboost-dev libmysqlclient-dev libxml2-dev libmcrypt-dev libicu-dev openssl build-essential binutils-dev libcap-dev libgd2-xpm-dev zlib1g-dev libtbb-dev libonig-dev libpcre3-dev autoconf libtool libcurl4-openssl-dev libboost-system-dev libboost-program-options-dev libboost-filesystem-dev wget memcached libreadline-dev libncurses-dev libmemcached-dev libicu-dev libbz2-dev libc-client2007e-dev php5-mcrypt php5-imagick libgoogle-perftools-dev libcloog-ppl0
Paste that into the terminal and grab a drink or do some curls while it loads; then the fun begins.
Step 2: Getting HipHop's source, preparing the environment
This part is fairly simple. Navigate to where you want the code to be placed (I just created a new directory in my home folder) and run these commands:
git clone git://github.com/facebook/hiphop-php.git cd hiphop-php export CMAKE_PREFIX_PATH=`/bin/pwd`/../ export HPHP_HOME=`/bin/pwd` export HPHP_LIB=`/bin/pwd`/bin cd ..
Explanation: This clones the hiphop-php git, downloading the source to the current directory. It then navigates to that directory, sets some environment variables to the current directory (pwd is Print Working Directory, not "password", mind you, and those aren't quotes, but backticks)
Step 3: Building the third-party libraries
This is the fun part. libevent must be compiled from source, which is simple enough. libcurl and libmemcached have to be patched, however, and the source files given in the HipHop wiki are a little dated. I'll do my best to explain what to do.
libevent
Just run this, assuming you're sitting one directory above hiphop-php (e.g., the last command run was the cd .. from above):
wget http://www.monkey.org/~provos/libevent-1.4.14b-stable.tar.gz tar -xzvf libevent-1.4.14b-stable.tar.gz cd libevent-1.4.14b-stable cp ../hiphop-php/src/third_party/libevent-1.4.14.fb-changes.diff . patch -p1 < libevent-1.4.14.fb-changes.diff ./configure --prefix=$CMAKE_PREFIX_PATH make make install cd ..
libcurl
The team makes note that you should keep your system time correct when configuring this. I'm not really sure why this is an issue, or why it is noteworthy, but it shouldn't affect you either way.
Run this:
wget http://curl.haxx.se/download/curl-7.21.2.tar.gz tar -xvzf curl-7.21.2.tar.gz cd curl-7.21.2 cp ../hiphop-php/src/third_party/libcurl.fb-changes.diff . patch -p1 < libcurl.fb-changes.diff
Which will download curl, extract it, and patch it with the required HipHop patch. Note: HipHop's guide tells you to configure after patching, but before fixing the source. Don't do that; we'll configure it later.
Now for some fixing. Go to curl's lib/ssluse.c, you need to make some edits.
cd lib vi ssluse.c
You need to add some lines right under the if block:
like this. Problem is, that source file doesn't match up so well with later commits, so we'll have to get creative.
#ifdef OPENSSL_NO_SSL2
failf(data, "openSSL was compiled without SSLv2 support");
return CURLE_SSL_CONNECT_ERROR;
#endif
So that it looks like: case CURL_SSLVERSION_SSLv2:
#ifdef OPENSSL_NO_SSL2
failf(data, "openSSL was compiled without SSLv2 support");
return CURLE_SSL_CONNECT_ERROR;
#endif
req_method = SSLv2_client_method();
use_sni(FALSE);
break;
Easy enough. Save it and close it, then cd .. back to the parent directory.
Compile and install libcurl like so:
./configure --prefix=$CMAKE_PREFIX_PATH make make install cd ..
libmemcached
This one is fairly easy. As with libevent, just download, extract, and compile:
wget http://launchpad.net/libmemcached/1.0/0.49/+download/libmemcached-0.49.tar.gz tar -xzvf libmemcached-0.49.tar.gz cd libmemcached-0.49 ./configure --prefix=$CMAKE_PREFIX_PATH make make install cd ..
Compile HipHop
Alright, home stretch! Except, there's one more thing to do: fix hiphop's extension system. Do this:
cd hiphop-php cd src/runtime/ext vi extension.cpp
Type in /ASSERT(s_registered_extensions)
Comment out the ASSERT, and replace it with:
if (s_registered_extensions == NULL) {
s_registered_extensions = new ExtensionMap();
}
So that it looks like:
void Extension::LoadModules(Hdf hdf) {
//ASSERT(s_registered_extensions);
if (s_registered_extensions == NULL) {
s_registered_extensions = new ExtensionMap();
}
Save it and close it.
Now, we do what we wanted to do from the beginning: compile.
Navigate out (cd ../../.. ) and do this:
git submodule init git submodule update cmake . make
That last piece will take a while, so go do some sword fighting on rolling chairs while you wait. If you have any errors with CMake, remove CMakeCache.txt and try again.
Once it's compiled, cp src/hphp/hphp and src/hphpi/hphpi to /usr/bin. Voila.
Coming up: Part 2: Using HPHP and HPhPi
Facebook's HipHop for PHP has been out for almost two years now, and surprisingly, has very little public interest, as far as I can tell. This is strange, considering just how useful it is when trying to boost page load times. Upcoming will be a tutorial on how to handle this; in the meantime, here's some salt to taste:
Page on Nginx + PHP-FPM + APC:
Document Path: /service/timeline.php
Document Length: 3083 bytes
Concurrency Level: 50
Time taken for tests: 5.68500 seconds
Complete requests: 33
Failed requests: 0
Write errors: 0
Total transferred: 109582 bytes
HTML transferred: 104822 bytes
Requests per second: 6.51 [#/sec] (mean)
Time per request: 7679.546 [ms] (mean)
Time per request: 153.591 [ms] (mean, across all concurrent requests)
Transfer rate: 21.11 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.5 0 1
Processing: 406 2550 1472.9 2552 4917
Waiting: 406 2549 1473.3 2551 4916
Total: 406 2550 1473.0 2553 4917
Percentage of the requests served within a certain time (ms)
50% 2518
66% 3328
75% 3599
80% 4198
90% 4627
95% 4821
98% 4917
99% 4917
100% 4917 (longest request)
Page with HipHop:
Document Path: /service/timeline.php
Document Length: 3083 bytes
Concurrency Level: 50
Time taken for tests: 5.500 seconds
Complete requests: 748
Failed requests: 0
Write errors: 0
Total transferred: 2425262 bytes
HTML transferred: 2309167 bytes
Requests per second: 149.59 [#/sec] (mean)
Time per request: 334.258 [ms] (mean)
Time per request: 6.685 [ms] (mean, across all concurrent requests)
Transfer rate: 473.55 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 2.2 1 25
Processing: 116 319 36.7 325 669
Waiting: 115 314 32.8 322 391
Total: 116 319 36.6 325 670
Percentage of the requests served within a certain time (ms)
50% 325
66% 333
75% 337
80% 340
90% 348
95% 354
98% 361
99% 369
100% 670 (longest request)
That's 23 times faster. Convinced?
Introduction
The project I work on, Dobble.me, is hugely motivated by optimization. Many, many posts on this site will relate to it, whether directly or indirectly. In it's source, everything is written from scratch, and no unnecessary bits or pieces are floating around. But even so, there's quite a bit of data to send to the client, and optimizing this transfer is key for high performance.
Thus, like many, many sites, Dobble has the JavaScript minified. If you don't know what that means, stop reading this immediately, and read this instead. Not only does this keep the source code compact and easy to parse, but it also prevents script kiddies from ripping our code without fully understanding how it works (obviously, a developer could easily "pretty" the code back, but it's the thought that counts).
The Problem
For a while, I was manually compiling builds of my JavaScript with the jsmin binary, packing all the files into one and naming them things like v1.js, v2.js, etc. This was tedious, and made it a huge PITA to do small patches or tweaks on the code.
It seemed reasonable, then, to use the JSMin PHP library to minify my code on the fly. Since I hold a strong belief in not using any unnecessary code, I opted to just use the PHP JSMin port directly, circumventing the other caching, URL, etc., stuff from the Minify library.
The result: Pages taking almost an entire second to load before the cache kicked in, CPU jumping to 5% just to minify. Not ok. Looking into it, it turns out the PHP library (or PHP in general) is just that inefficient. Sure, I still needed to implement the APC support to cache the results, but regardless, this minification library was not going to fly.
The Solution: JSMin PHP Extension
Doing some research, I found a port of the original C library. Essentially, it's a PHP extension that integrates with PHP to provide native, optimized minification.
The setup was not too difficult:
wget http://www.ypass.net/downloads/php-jsmin/php-jsmin-1.0.tgz cd php-jsmin-1.0 php5ize sh ./configure make make install
Then, add it to your php.ini file like so:
extensions=jsmin.so
There was only one issue in this implementation, where there was an inconsistency in the code as compared with the original JSMin code, where lines such as:
var html = "This is an example\ of a multiline string.";would receive double new lines, instead of losing a new line. Whatever the reason, this was fixed with a simple str_replace:
function _jsmin($data) {
$data = jsmin($data);
$data = str_replace("\\\n\n", "", $data);
return $data;
}
Now, the fun part: benchmarking. I grabbed a sample chunk of the codebase and ran some tests to compare time and code output length. Here are the results:
Old PHP JSMin port: 0.50116896629333 seconds. Length: 53555
New code using PHP JSMin extension: 0.0089550018310547 seconds. Length: 53528
As you can see, the extension is much faster, and (thanks to removing all my \\\n occurrences) a little bit smaller on the output.
A special thanks goes to yPass.net for the creation of the extension. As you can see, the results are quite impressive, and coupling this with some careful caching, I think we should find both load times and deploy times greatly decrease ;)