php. nginx. linux. optimizing the web. Home Archive About

Guide: Getting started with HipHop for PHP

01-04-12

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.

  • In vi, type /SSLv2_client_method and press enter. It'll take you to a switch block.
  • Right before req_method = SSLv2_client_method();, add the following code:
  • #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

HipHop for PHP: You need this (Benchmarked against PHP-FPM)

01-03-12

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?

Optimizing JS minification with the JSMin PHP Extension

01-03-12

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 ;)

older posts