Benchmarking Asyncronous PHP vs NodeJS Properly

This article was jointly written by Samuel Reed, a freelance JavaScript developer, and Kevin Ohashi, Founder of Review Signal. NodeJS testing was handled by Samuel Reed and PHP by Kevin Ohashi on the same VPS.

I recently read Phil Sturgeon's Benchmarking Codswallop: NodeJS v PHP and was interested to see that PHP performed pretty well compared to NodeJS once it was run asynchronously.

My first reaction was to share the article with my good friend Samuel Reed who is a JavaScript and NodeJS lover to see what his thoughts were. He instantly wrote back to me "Not that it matters, but that bench was completely broken node-side, we have an absurd connection pooling default."

So we decided to run the test ourselves and he would fix the NodeJS settings. I would follow Phil's instructions for the PHP side as best as possible.

The Server

We used the same VPS. It was a 512MB droplet from DigitalOcean in NY2.

PHP

If you are wondering, to get ReactPHP running with composer, simply do the following once you're in the folder with all the files:

curl -sS https://getcomposer.org/installer | php

php composer.phar install

It's not in Phil's instructions, but that's the missing part he alludes to.

Blocking PHP Results:

real 3m21.114s
user 0m8.217s
sys 0m0.496s

Non-Blocking PHP (ReactPHP) Results:

real 0m11.971s
user 0m10.897s
sys 0m0.704s

The PHP results were fairly similar to Phil's result. That's good, because we haven't changed anything and tried to replicate his experiment.

NodeJS

I used two versions of Node for the test - the latest stable tag at 0.10.22, and the latest unstable, at 0.11.8.

Around version 0.4, Node added connection pooling and a concept called an Agent. An agent can be added to an individual http request, or to all requests on the server. For example, if I had a free or limited account on some API, I might not want to hit it with more than 10 simultaneous connections at a time. In that case, I would assign an Agent to the request and set its `maxSockets` property to 10.

Unfortunately, the `globalAgent`, which runs for all http requests without an explicit Agent (which is what you do about 99% of the time), has a limit of 5. That's right - just 5. One of Node's biggest selling points is speed, yet outgoing requests are completely hamstrung by this absurd opt-out connection pooling scheme that most people miss. It's not like these are expensive database connections - these are outgoing http requests, and all of them share the same 5-connection limit.

As a general rule, setting `http.globalAgent.maxSockets=Infinity` is what you want. It's one of the first things I do in any program that makes outgoing requests.

Of course, I'm not the first person to rant about this; not by a long shot. From famed NodeJS developer substack:

This module disables a lot of infuriating things about core http that WILL cause 
bugs in your application if you think of http as just another kind of stream:

http requests have a default idle timeout of 2 minutes. This is terrible if you 
just want to pipe together a bunch of persistent backend processes over http.

There is a default connection pool of 5 requests. If you have 5 or more extant 
http requests, any additional requests will HANG for NO GOOD REASON.

hyperquest turns these annoyances off so you can just pretend that core http is 
a fancier version of tcp and not the horrible monstrosity that it actually is.

I have it on good authority that these annoyances will be fixed in node 0.12.

Luckily, this behavior is now fixed as of about four months ago, and the last few Node unstable releases contain the fix. The next stable release, Node 0.12 is around the corner and thankfully will make a big difference in lots of users' applications.

NodeJS Results

v0.10.22 (default maxSockets=5)

real 0m30.691s
user 0m10.089s
sys 0m0.628s

v0.10.22 (maxSockets=64, mentioned in the followup article)

real 0m11.299s
user 0m9.973s
sys 0m0.768s

v0.10.22 (maxSockets=Infinity, this is what most servers should be running)

real 0m10.339s
user 0m9.269s
sys 0m0.624s

v0.11.8 (default is maxSockets=Infinity)

real 0m7.480s
user 0m6.372s
sys 0m0.680s

Node v0.11.x brings along with it a host of http optimizations that make it even faster than v0.10.22 without connection pooling.

Conclusion

At this point, as Phil Sturgeon mentioned, this is more of a network test than anything else; although latest Node is about 60% faster in this test than ReactPHP. Of course, when choosing a framework, one must consider much more than raw speed in singular benchmarks, but they can be entertaining.

We thought it best, given the popularity of the original post, to set the record straight about how it would perform if the NodeJS code were corrected. In general, if you're running Node v0.10 or below, set `http.globalAgent.maxSockets=Infinity` (and the same for https, if you use it), unless you absolutely have a need for connection pooling.

The following two tabs change content below.
avatar
Kevin Ohashi is the geek-in-charge at Review Signal. He is passionate about making data meaningful for consumers. Kevin is based in Washington, DC.





5 thoughts on “Benchmarking Asyncronous PHP vs NodeJS Properly

  1. avatarRicardo Machado

    Well, just the fact that PHP almost meets the same responsiveness as Node is already pretty good 😐

    Now, I think that the benchmark should check/mention the Servers resources’ usage.

    Reply
  2. avatarGlen

    CodeIgniter with Memcached cache enabled, running in FastCGI / FPM under an NGinx server with FastCGI cache enabled and a well devised relational or NoSQL back end, cannot be found to be much slower than Node.js. The use of client side cache and CDN services are not mentioned above but it is not a secret that PHP when configured correctly, can scale and match any Node.js solution In both speed and scalability.

    Reply
  3. avatarMichael

    @Glen – I would be delighted to see what your configuration for Nginx and PHP5-FPM. Particulary Nginx workers and worker connections as well as FPM buffer configuration when comparing the two while trying to server 1 billion request per day. Since asynchronous I/O is important when it comes to high availability– particularly serving mobile applications.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

Current day month ye@r *

Loading...

Interested in seeing which web hosting companies people love (and hate!)? Click here and find out how your web host stacks up.