April 26th, 2010

Faster, Windows PHP! Kill! Kill!

Tom Boutell
Chief Software Architect
IN WHICH our hero is narrowly saved by vague memories of a really dull presentation by some guys in suits.

You may recall my recent post on speeding up PHP on Linux without changing your PHP code. Those techniques have become standard practice around here, and the article has since been retweeted, linked to, engraved on an unobtanium disc embedded in a space probe to be launched next week by the Malaysian space forces, et cetera. So it seems we have the Linux PHP thing down to a science, for now.

But not every shop runs Linux, and one of our clients has asked us to deploy in a Windows environment. We've deployed PHP sites for them before, but that was in the Symfony 1.0 days, and there just weren't nearly as many class files to compile. When we set out to deploy a modern site built with Apostrophe, our content management system based on Symfony 1.4 and Doctrine, we were reminded that out-of-the-box PHP is slow, slow, slow.

I would gladly have addressed this the same way we do it on Linux: FastCGI, APC and Apache's worker thread MPM. And in fact Apache for Windows seems to only support a special threaded MPM under Windows anyway, so that part is already familiar.

Unfortunately, the official PHP infrastructure for Windows has, frankly, undergone a bit of code rot lately:

The PHP binaries don't seem to include a CGI/FastCGI binary! Shoot. Well, maybe APC alone will be enough. But APC is not standard with the Windows PHP binaries, so I need to get it from PECL. Okay, but the PECL for Windows site is "temporarily out of service." A new solution is coming, but it's not here yet.

Searches led me to Pierre Joye's folder on downloads.php.net. Pierre is a core PHP developer, so I trust him, no problem there. But the version of APC found there just locks up Windows PHP, at least in my tests. I investigated alternative PHP bytecode caches and wound up with xcache. xcache worked for a while, then started spewing errors about classes being redefined, and always began doing so again within a few page views after each restart of Apache.

Yuck!

Fortunately, by this point a vague memory was stirring somewhere in my geeky cortex. I was in Paris... I was full of pain au chocolat... I was very sleepy... two guys in suits were droning on and on about something while the crowd ignored them... AHA! Those Microsoft guys at Symfony Live! They were talking about speeding up PHP on Windows. They even mentioned hiring Pierre Joye to help out. But they didn't demonstrate anything on Symfony, so I wasn't paying a lot of attention—

Just enough as it turns out. The deal is this: Microsoft knows that PHP has, historically, sucked on Windows. And they have taken steps. And this is good news for the rest of us.

PHP can now be installed via the Microsoft Web Platform installer. That gives you single-threaded PHP running via fastcgi— exactly like my Linux setup, except for the bytecode cache part. And now the new Windows Cache Extension for PHP delivers a bytecode cache, equivalent to APC.

The end result is definitely in the ballpark with the performance of my Linux solution. Since I haven't tested them on the same hardware (both boxes were VMs, and they were provided by different datacenters), I cannot say for certain which was faster. The Microsoft speakers at Symfony Live characterized it as better but still not as fast as the best PHP setups on Apache. At any rate it's a big improvement and well worth your time if you need to host on Windows.

So here's a HOWTO for configuring Symfony and PHP on Windows with good performance:

To configure IIS for Symfony:

1. Make sure IIS is installed. I know, right?

We want IIS 7. IIS 7 features an excellent importer for mod_rewrite rules, and Symfony loves mod_rewrite rules.

2. Install PHP from the Microsoft Web Platform. Click that link from IE running on your server (hint: remote desktop connection) for the easiest download-and-install process.

3. Install Windows Cache for PHP to get acceptable speed from the above. I installed version 1.0 because 1.1 is in beta. The beta does look interesting because it has an alternate PHP session handler that uses shared memory. You'd have to configure symfony to actually use that feature I believe (welcome to factories.yml).

4. Let's say your project's short name (aka "Unix name") is mysite. So copy your Symfony project to c:\mysite. (I'm assuming a virtual machine for a single client project. Choose a better folder structure if you're deploying lots of sites on this box.) You can set up cygwin to allow ssh access and rsync those files with project:deploy, and in fact we do, but that's a subject for another day. FTP also works.

5. Launch the IIS manager (Start -> Administrative Tools -> IIS Manager).

6. Open the server's icon, then "Sites," then delete the "default site" entry.

7. Add a new site entry called mysite. Map the site's web folder to be the web subfolder of the Symfony project which you should have copied to c:\mysite by now. So the web folder path would be c:\mysite\web. Assign it to port 80. Do NOT set the web folder to be the root of your project! That is very dangerous as it exposes your databases.yml file. Point it to the web subfolder.

8. Always clear your Symfony cache after copying a site to a new server. At the command shell (start->run->cmd.exe), type: cd \mysite php symfony cc 10. PHP needs write access to c:\windows\temp to store session data in order for logon sessions to work. You should have no trouble here on a new setup with just IIS I suspect, but if you previously ran Apache you might need to clean up. Clobber any existing session files in c:\windows\temp. Note that this is also the folder where the PHP error log file is found:

c:\windows\temp\php-errors.log

You can watch that file using a Cygwin shell and the tail -f command, or peek at it as needed with a text editor.

9. Try the site out, with a frontend controller name in the URL. You'll get the home page but subpages won't work yet without manually putting the controller name back in the URL. In IE running on the server itself, visit:

http://localhost/index.php

10. We need to add the mod_rewrite rules that remove the need for `/index.php`. But IIS doesn't have mod_rewrite. Fortunately IIS7 has its own URL rewriting feature... and it can import your rules.

In the IIS manager, double-click mysite, then double-click "URL Rewrite." In the "Actions" area at right, click "Import Rules" under "Inbound Rules."

Edit: if "Inbound Rules" is missing, close "URL Rewrite" and then open that feature again. Also try adding one empty rule manually first. The "incredible disappearing/reappearing 'Inbound Rules' feature" appears to be a bug in the IIS manager.

Paste the mod_rewrite rules from your web/.htaccess file. IIS will do a pretty snazzy job of converting them when you click Import (but if you have fancy rules that go beyond the usual Symfony rules, give the end results a careful look-see). Then click "Apply."

Edit: IIS won't tell you, but the settings you just created live in a file called web.config in the web subfolder of your project. You must not crush this file when you sync your project. Add the file to your project, or rsync exclude it if you're using cygwin and project:deploy. Since you might want to add more rules to it manually I generally recommend the former. But one disadvantage of adding it to your project is that any edits you make in the GUI will get clobbered on future syncs if you don't manually copy the resulting changes from web.config on the server back to your project.

11. Try the site again. You should be able to browse the entire site now.

12. Whoops, you can't log in because IIS is hijacking the 401 Unauthorized error page. To fix that, go back to the IIS Manager pane for mysite. Double-click "Modules" and remove the "Custom Error" module.

Now your site's login page will appear properly. Also your custom 404 and 500 error pages.

That's it - you should be ready to go with much speedier Windows PHP.

Note that for the time being the Microsoft Web Platform seems to offer only PHP 5.2.13. This is a stable and sane choice and until recently I would have done it no differently. But PHP 5.3.2 is much better than 5.3.0 and 5.3.1 and offers new features that matter— improved memory management, faster performance and new PHP language features that the forthcoming Symfony 2.0 requires. Hopefully PHP 5.3.x will appear in the Web Platform installer soon.

(*) Yes, I sorted through the maze and found versions of PHP and APC that were supposedly for the same C runtime, et cetera. Still no love. xcache was better, but alas the redefined class errors put paid to that too.

Tom Boutell
Chief Software Architect

Check out another article
April 14th, 2010
Open Source Help Desk
March 28th, 2010
Mix Tape