Optimizing Wordpress Performance & Speed
We start with a fresh copy of Wordpress 2.03, fresh from the Wordpress download center. Our goal is to figure out what parts are slow and can be improved. We’re not interested in sacrificing features or existing code for speed, like Lightpress has done. Rather, we’d like to identify the worst performers and improve them, if possible, in the default install.
Our microscope will be XDebug, a PHP performance tool that we’ve installed as a zend extension on our server. It’s a C module that keeps track of the time spent running PHP code, so we can grab and analyse its output to determine what Wordpress is doing under the hood.
Our data set will be all the posts, comments, and pages from this blog. Currently, that is 1405 posts with 6977 comments and 4 pages. This should provide ample material for testing. The database will be optimized after importing.
Initial profiling
Loading the main Wordpress page produces the following traces:

10,000 calls to preg_replace, 6,410 to str_replace, and 2,465 to strstr.

wptexturize is the heaviest function at 24.4% of its own code, 5.5% on mysql, and 4.5% on apply_filters. Template loading, and php compilation (require_once) are together 30-40% of the loading, as well.
First optimizations
The first thing I notice is that index.php sets a config variable and just includes another file. Why not just move that define() into wp-blog-header.php? It’ll save a function call, and not a lot of time, but it’s cleaner. Also, the ABSPATH define should be done before any other files are included, and thus copied from wp-config.php to index.php to save a few more function calls. There’s a line that sets the timer with an extra assignment statement.
Nitpicking’s not going to get me anywhere. Let’s take a look at wptexturize. We note that we can replace some preg_replace calls with str_replace, because only static strings are replaced, so we add the ['\'s','’s'] to the cockneyreplace array hack. We apply this process to all static strings. We can also convert the strings from slower dynamic double quotes to faster static single quotes, and put the dynamic section into another array, like the static section.
These simple operations reduce the time spent inside wptexturize from 24% to 16%, and the time from 600ms to 200ms. We’ve also reduced the number of preg_replace calls dramatically, from 10,439 to 3,289 and total time from 74ms to 36ms. We’ve gone from 54ms of 6,410 str_replace calls to 29ms of 1,405 calls. Why is this? By calling each function only once we save a lot of extra PHP operations and just hand off a bunch of data to fast, underlying C functions. Interestingly, using array_walk/array_map is not faster than a plain loop.
Here’s the new wptexturize function:
<?php
function wptexturize($text) {
$next = true;
$output = '';
$curl = '';
$textarr = preg_split('/(<.*>)/Us', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
$stop = count($textarr);
for($i = 0; $i < $stop; $i++){
$curl = $textarr[$i];
if (isset($curl{0}) && '<' != $curl{0} && $next) { // If it's not a tag
// static strings
$static_characters = array('—', ' — ', '–', 'xn--', '…', '“', '\'tain\'t', '\'twere', '\'twas', '\'tis', '\'twill', '\'til', '\'bout', '\'nuff', '\'round', '\'cause', '\'s', '\'\”, ' ™');
$static_replacements = array('—', ' — ', '–', 'xn–', '…', '“', '’tain’t', '’twere', '’twas', '’tis', '’twill', '’til', '’bout', '’nuff', '’round', '’cause', '’s', '”', ' ™');
$curl = str_replace($static_characters, $static_replacements, $curl);
// regular expressions
$dynamic_characters = array('/\'(\d\d(?:’|\')?s)/', '/(\s|\A|”)\'/', '/(\d+)”/', '/(\d+)\'/', '/(\S)\'([^\'\s])/', '/(\s|\A)”(?!\s)/', '/”(\s|\S|\Z)/', '/\'([\s.]|\Z)/', '/(\d+)x(\d+)/');
$dynamic_replacements = array('’$1','$1‘', '$1″', '$1′', '$1’$2', '$1“$2', '”$1', '’$1', '$1×$2');
$curl = preg_replace($dynamic_characters, $dynamic_replacements, $curl);
} elseif (strstr($curl, '<code') || strstr($curl, '<pre') || strstr($curl, '<kbd' || strstr($curl, '<style') || strstr($curl, '<script'))) {
// strstr is fast
$next = false;
} else {
$next = true;
}
$curl = preg_replace('/&([^#])(?![a-zA-Z1-4]{1,8};)/', '&$1', $curl);
$output .= $curl;
}
return $output;
}
?>
Update: As of change #4511 in 11/21/06 Ryan Boren merged this into Wordpress core code. So you now have it!
What’s next
A new look at the numbers shows the next biggest culprit is apply_filters. Even on a default installation, it’s slow, taking 106ms of its own time, and 438ms total. Unfortunately, there doesn’t look to be an easy way to optimize it. Other slow spots, like get_settings and list_cats are likewise difficult to immediately improve. An easy way to increase performance would be to rewrite the plugins architecture and improve the filters mechanism, but that would mean an API change.
Some good news
It’s not all bad though; here’s how the change to wp-texturize performs, tested on PHP 5 windows on a 1.8 GHz machine, with 10 large entries on the homepage:
ab -n 100 -c 1 http://localhost/test/
Document Length: 190017 bytes
Requests per second: 0.78 [#/sec] (mean)
Time per request: 1278.438 [ms] (mean)
Now, here’s the default install of Wordpress 2.0.3:
ab -n 100 -c 1 http://localhost/test/
Document Length: 189786 bytes
Requests per second: 0.77 [#/sec] (mean)
Time per request: 1297.344 [ms] (mean)
Conclusion
While the new wptexturize function performs well on a server with an opcode cache, the performance increase is lost among other considerations on an “out of the box” configuration. Also, a quick glance at the Wordpress core code shows no easy patches to increase performance by large orders, just design ideas that could be gradually improved over time.
Lightning & Thunderstorm Video
There was some decent looking lightning in a storm we had here in Phoenix a few days ago, so I made a little video clip of it:
The Best of Craiglist Postings
Did you know that Craiglist postings can be voted “best of” by the system’s users? The Best of Craiglist is the perfect place to go to find hilarious classified ad rants. As a word of warning, the subject matter is often sexual or adult. Still, there’s plenty a work safe gem to be found:

For example, there’s a desperate hedge fund manager with actual barrels of crude oil in his apartment to get rid of:
Date: 2004-12-02, 8:20AM EST
Sexy UES hedge fund manager willing to s*ck/f*ck his way out of a crude long position. they told me “it would go up forever”. in any event i have barrels of crude warehousd in my apt. willing to trade s**ual favors for 54.95 a barrel … inquire within this is in or around NYC
Then there’s the naive family guy who wants to give away the doubles he accidentally ordered of family photos:
Date: 2004-06-14, 11:37PM EDT
Hi. I just accidently got doubles for my 35MM color prints which I shot with my Kodak digital camera. I don’t want the doubles so I figured someone on here would want them. They’re no nudes or anything like that. Just pics of me with friends and family, then a few pics of my cats. If you want them email me your home address and i’ll send them off to you. this is in or around bronx
Then there’s the old iPod for a fake girlfriend scheme:
Date: 2004-03-14, 12:50AM EST
Hi, I’m having my parents come visit me sometime in the next two weeks and have lied and told them I am dating someone I am in love with. You will only have to come to one dinner. In exchange for this I will buy you an IPOD – yes new – we walk into the store together and buy a new IPOD. Let me know if this interests you, and if you want to be in a loving relationship with all the benefits it brings
I want to pretend we are totally in love. I am 24, swm, a grad student, italian-american, (not a guido), athletic build. Send pics and i will send you mine, note I check email basically every 3 hours. You should be in your 20’s and athletic (great butt and legs are my main interest when I say athletic).
Hilarious. If you’re an avid gamer, you might find this funny:
Level 72 Paladin Seeking 42+ Rogue, Druid, and Sorceress – m4ww
Date: 2006-06-20, 4:36AM PDTI am seeking a level 42 or above rogue, druid, and sorceress to help me assault the fortress of Mordria, and for hot kinky s3x. I am the sole holder of the Axe of Fragyholt and am a level 72 Paladin equipped with Def+ 52 plate mail [...]
Finally, the winner of the bad title award:
TRADE: My Coke for Your Pot
Date: 2006-05-16, 6:56AM EDTI have a 12-Can “Fridge Pack” of Coca Cola Zero. What I need is a heavy duty aluminum or non-stick cooking pot suitable for everything from making spaghetti to steaming clams. Will consider other offers!
With enough browsing you’ll find some real gems.