WordPress comments_template() and wp_list_comments() Performance

This thread on memory usage while executing WordPress’s comments_template() raised my awareness of performance issues related to displaying comments on posts in WordPress. The first thing to know is that all the comments on a given post are loaded into memory, even if the comments are paged and only a subset will be displayed. Then comments_template() calls update_comment_cache(), which has the effect of doubling that memory usage. Finally, wp_list_comments() and the Walker_Comment class can take a surprisingly long time to iterate through a long list of comments.

The OP worked around his problems by commenting out the call to update_comment_cache(). My interest was in reducing the CPU usage on each page load for posts with lots of comments, so I focused on wp_list_comments() and the Comment_Walker. Considering the cost of iterating the comments and that most pageviews don’t result in new comments, there seemed to be some benefit in persistently caching their result. Moreover, it’s pretty easy. After creating a plugin with the following, you can simply replace the calls to wp_list_comments() in your theme to a call to cached_list_comments().

define( 'COMMENT_CACHE_VERSION' , 1 );
function cached_list_comments( $args )
	global $post;

	$cache_array = get_post_meta( $post->ID, '_cached_comments', true );	
	if( ! is_array( $cache_array ) || $cache_array['version'] < COMMENT_CACHE_VERSION || empty( $cache_array['html'] ) )
		wp_list_comments( $args );
		$cache_array = array( 
			'html' => ob_get_clean(),
			'date' => time(),
			'version' => COMMENT_CACHE_VERSION,
		update_post_meta( $post->ID, '_cached_comments', $cache_array );

		$freshly_cached = TRUE;
	echo $cache_array['html'] . '<!-- Comments cached '. ( $freshly_cached ? 'fresh now ' : '' ) . date( DATE_RFC822 , $cache_array['date'] ) .' -->';

But what happens if somebody comments, or if you approve a comment, or if anything happens to change what should be displayed for comments? Easy, just kill the cache. This code should do the trick:

function cached_update_comment_count( $post_id )
	delete_post_meta( $post_id, '_cached_comments' );
add_action(  'wp_update_comment_count' , 'cached_update_comment_count'  );

This won’t work for paged comments, and if you change your theme you’ll have to increment the COMMENT_CACHE_VERSION constant so that it regenerates the comment cache with the new markup. And you’d also be smart to close comments on old posts. The code would improve performance even more if I could block the query that fetches all the comments on the post, but even without doing that this caching has saved up to five seconds of page generation time on posts with 400 or so comments. Not bad for about 25 lines of code.

Props to Vasken Hauri, who quite a bit of legwork on this for me and our employer.