Faster PHP behind FastCGI
A few years ago, Jan Kneschke came up with the idea of using lighttpd's X-Sendfile to send dynamic content without copying it several times. I liked the idea and used it as basis of my framework. It seems, there are now some immature implementations of this idea on the lighttpd bug tracker. All of these implementations also use the shared memory, with the difference that I haven't used PHP's tempfile() function, but rather exported lighttpd's client-file-descriptor to the PHP scope in order to use it as the file-name of the temporary file. It could also be the IP or something else but the client-fd is the most unique identifier inside of the webserver <-> PHP construct.
I've just published my own PHP version with a lot of improvements and optimizations. To write the content quickly, I also added a new function ob_fwrite() to write the contents of the ob-buffer to an opened file descriptor like this:
ob_start(null, 0x20000); echo 'Write the content into the buffer'; $fd = fopen('/pipe/' . $_SERVER['CFD']); if (!BUFFER_CONTENTS) { ob_fwrite($fd); ob_end_clean(); } else { $buffer = ob_get_clean(); } fclose($fd); $ref =&$_SERVER['HTTP_ACCEPT_ENCODING']; if (CACHE_CONTENTS) { $path = System::getCachePath($_SERVER['CFD']); rename('/pipe/' . $_SERVER['CFD'], '/cache/' . $path); exec('gzip -c '.$path.' > '.$path.'z'); if(isset($ref) && false !== strpos($ref, 'gzip')) { header('Content-Encoding: gzip'); $path.= 'z'; } header('X-Sendfile: /cache/' . $path, true, $http_status); } else { if(isset($ref) && false !== strpos($ref, 'gzip')) { header('Content-Encoding: gzip'); exec('gzip ' . $path . ' > ' . $path . 'z'); $path.= 'z'; } header('X-Sendfile: /pipe/' . $_SERVER['CFD'], true, $http_status); }
Please note that /pipe is a tmpfs mountpoint!
The same works, of course, with nginx's X-Accel-Redirect. Reducing the number of memory copies improves the performance alot! There are several copies before the content is finally transfered to the client; which the following sketch illustrates:
PHP ECHO > FCGI SAPI > WEBSERVER > CLIENT
The new way, the content takes, looks like this:
PHP ECHO > OB BUFFER > SHM > WEBSERVER > CLIENT
..., or if the content is already cached:
PHP > CACHE HIT > WEBSERVER > CLIENT
Thick arrows in the sketch above are expenisve copies.
The whole thing runs contrary to the best practices, published by Yahoo!. It's not possible to send the buffer early this way. This could mean, that the client can't start downloading the style-sheets and scripts as fast as possible. But as far as I can see, most of the time should not spend in the backend but with sending the data to the client. Everything else can be optimized away, like optimizing hanging database queries and so on. I have already raised this idea a while ago on the Yahoo! developer forum.
Incidentally, this optimization only makes sense for single-threaded web-servers that communicate over the slow FCGI bridge. Apache and other thread-based servers that integrate PHP as a module, can not save much here. Additionally, Apache would need the X-Sendfile-module.
Update of June 12
I used the $_SERVER["CFD"] variable in the example above. In order to make use of it, lighttpd and nginx must be patched. You could also use tempfile() instead but this is annoying because of the aditional required delete-operation of the file. If every file descriptor gets it's own unique file, this is much cleaner. Here you can get the Patch for lighttpd 1.4.28.
You might also be interested in the following
- Combine CSS and JavaScript with lighttpd
- PHP Hacking
- Dynamic thumbnail generation on the static server
- Is it possible to avoid query parsing inside of MySQL?
Sorry, comments are closed for this article. Contact me if you want to leave a note.
3 Comments on „Faster PHP behind FastCGI”
robert,
please publish this patch
thank you in advance
li, I patched both, nginx and lighttpd. I posted a lot of patches to the lighttpd bug tracker and some are already merged into the current version. Anyway, I think you're asking for a patch which exports the client-fd to the PHP scope, right? This can be done very easily, if you need this small patch, I'll publish it.
do you have any changes to lighttpd code base ? if so can you published them