The importance of micro optimizations

Many years ago when coding in C/C++ or assembler I learned one important rule - DO micro-optimize your code. Unlike macro scale optimization, which should be done after your code is generally working and feature complete, micro optimization should be applied as you go. Why? Because sticking to a few simple rules (which don't affect the overall way in which the code works) as you code is easier than having to fish for the small inefficiencies in your source later on. Some of the optimizations can be performed by the compiler (like taking a constant expression out of loop) but some still depend on the coder doing the Right Thing™.

It seems that, a month ago, I proved that I had forgotten the rules I learned and applied in the past by committing code which affected Mono ASP.NET's performance in a really major way. I'm posting this blog entry as a memento to myself and to others who, like me, might have forgotten that such rules still apply in the 21st century despite all our smart compilers, sophisticated virtual machines and runtimes.

The committed code did (eventually) something like this:

void Method (string s) 
{
	string s2 = GetSomeString ()

	CallMethod (s + '@' + s2);
}

The CallMethod line caused (as we discovered yesterday) a drammatic decrease in MojoPortal's performance - its home page would open on my computer, using local connection in 2.56s (yes, seconds) on average. Gonzalo went hunting for the issue and this morning I read a mail from him pin-pointing (after lots and lots of time wasted on it) the performance loss to that very line. I fixed the issue in our code and suddenly the load time for the page went down to ~107ms (yes, milliseconds)! One might ask whether it was because concatenating two strings and a char is so inefficient? Yes, it is inefficient (as the operation has to convert a char to a string, then allocate and concatenate two more strings) but not enough to justify such performance loss. The key here is the frequency at which the code is being called - GetSection gets called hundreds (if not thousands) of times during one request, and the operation's summed up time contributes to the peformance loss.

This is exactly what I described at the beginning of this post - a micro optimization, a right thing to do in the right place. It can be compared to adding two integer or float variables whose values don't change in a loop which takes thousands of iterations and is invoked by some code very often. The addition is not very expensive if your code performs it once in a while, but in the scenario when it repeatedly calls the code, you will see major loss of performance. The solution is to take the operation outside of the loop. This is precisely what happened here.

So, even in this day and age - ladies and gentlemen, do micro-optimize and pay attention to where your "slightly inefficient" code lives :)

Update:

As Jon suggested below, I should have suggested possible solutions to the issue:

  • CallMethod could take 3 arguments instead of one (depends on what the method does, of course) - that was the solution chosen in this instance to fix the issue as the string concatenation wasn't at all necessary.
  • Using "@" instead of '@' would avoid costly (culture-sensitive) conversion of a char to string

Update:

I'm officially an idiot. The real cause of the slowness was that we've been bypassing the cache because of different key being generated to query cache and another to insert into it. My only excuse - lack of sleep :P (I know, a poor one :D). The post's main point still stands, though - just ignore the rambling about string concatenation. Now, where's my brown paper bag...