Click here to Skip to main content
15,886,067 members
Articles / Programming Languages / C#
Technical Blog

The Importance of Useless Micro-optimization

Rate me:
Please Sign up or sign in to vote.
4.59/5 (7 votes)
3 Oct 2014CPOL5 min read 18.6K   7   8
The importance of useless Micro-optimization

Micro-optimization is “the process of meticulous tuning of small sections of code in order to address a perceived deficiency in some aspect of its operation (excessive memory usage, poor performance, etc.)”

We’ve all seen these kind of debates: Is X faster than Y? Should I have to replace A by B in my code? It’s not specific to any language, but it’s a real question for all developers. Programmers are generally advised to avoid micro-optimization, unless they have a solid justification. Yes, it depends.

Most of the time, micro-optimizations are confirmed by benchmarks: a very specific code section is executed thousands/millions to illustrate the problem and to confirm initial hypothesis. A is X times slower than B. Of course, in real world applications, we rarely call one piece of code so many times, so this stuff may seem inappropriate. But the trouble is that the extra N-milliseconds is CPU time on the server. A web server could simply be idle waiting for request, processing other requests, … but instead it is busy executing the same inefficient methods over and over. Those N-ms can even become N-s during peaks load.

Improving performance of an application is an endless road. It’s a very time-consuming activity and don’t forget that’s it’s not the core of your business: Your boss wants you to deploy new features and not pointless optimizations. It’s very common to spend several hours (days?) to reach your performance goals.

By performance, I mean something that limits scalability of your application. It could be CPU, network IO, Memory … You may know that all applications (systems) have throughput limits. Your job, as a chief performance officer, is to keep fast response times in all circumstances (unexpected system difficulties, light load/heavy load) and to be far from these limits.

The performance of a code section is a mix between frequency & efficiency. I don’t care to write an inefficient code if it’s rarely used but I’m really concerned by most called operations. Have you already counted how many times ToString() is called in your .NET project ? We can’t blame someone else for having written un-optimized code that we want to use in our –different- context. For example, .toString() has maybe not been coded to be used as a key for Dictionaries.

Since 10 years of professional programming, I’ve seen serious performance problems. “I don’t understand why it takes 5 sec to generate my page each evening. Everything is cached and I’ve run many benchmarks: all my methods take less than 5ms.” Yes, but these methods are called 100 times while generating your page. Simply speaking, 5 ms is too slow for your context !

Even with a clean architecture, it’s sometimes difficult to determine the critical rendering path. That’s why I’m a big fan of profiling tools like Visual Studio Profiler: they give you a complete overview without changing your application. Collected data during a load test session, can be precious.

I often see micro-optimization as a challenge: let me try to significantly improve the performance –and scalability- of your application by changing the fewest lines as possible. If I can’t do this, I’m either not good enough or there are no interesting optimizations.

Examples in .NET

A few months ago, I already explained on the Betclic Techblog one bad experience we had with Structuremap and Web API. To summarize, we’ve significantly reduced CPU usage of a web application, just by splitting IoC bindings into multiple containers.

More recently, I was mandated to do a performance review on a WCF service consuming too much CPU & memory. Without any doubts –thanks to my favorite profiler- , one method is causing several alerts & warnings.

buildmessage

This method is in a base class. Because the project follows the Onion architecture, dependency injection is heavily used here to manage dependencies (at core, service & repository layers). As a consequence to be in a pure stateless world, this method can be invoked several times during each request processing. The average call rate is approx. 150/sec during peaks load, so this is a good candidate for a micro-optimization.

How to improve this method? It doesn’t look so bad at the first view ….

A first idea could be to optimize string operations. We all know the obvious beginner mistakes of string concatenation. Memory allocations are fast on modern PCs, but they’re far from free. String.Join()/StringBuilder seems better in this case.

A second better idea is to remove Enum.ToString(). The type of Legislation/Brand/Platform is enum. The method Enum.ToString() is called implicitly in this code section (during concatenation). An interesting article on CodeProject explains the troubles with Enum.ToString().

A few minutes later, I finally produced this extension method (Gist):

public static class EnumExtensions
{
    public static class EnumCache<TEnum>
    {
        public static Dictionary<TEnum, string> Values = new Dictionary<TEnum, string>();
        static EnumCache()
        {
            var t = typeof(TEnum);
            var values = (TEnum[])Enum.GetValues(t);
            var names = Enum.GetNames(t);
            for (var i = 0; i < values.Length; i++)
            {
                Values.Add(values[i], names[i]);
            }           
        }

        public static string Get(TEnum enm)
        {
            return Values[enm];
        }
     }

     public static string AsString<TEnum>(this TEnum enm) where TEnum : struct, IConvertible
     {
         return EnumCache<TEnum>.Values[enm];
     }
}

Let’s run benchmarks on 50000 iterations (check the gist for the full source code):

vsprofilerenum

consoleoutputenum

Faster and less allocated memory, not so bad. (Note: It’s even a little better than the original CodeProject article but with less possibilities). For sure, a .NET Guru may find a more efficient way, but it’s enough for me: I reached my performance goal. We now have an optimized ready-to-use extension method with less than 30 lines of code, that’s 40 times faster. Very well done!

But, we’re ALL WRONG !

Optimizing code means you have to think differently. Simply create a property (eventually lazy), computed just once per request, and inserted somewhere in the scope. That’s it.

In every project, there are code conventions: this class to manage logging, this one to format date format, this one to translate string… We should be very careful with these kind of helpers, extension methods, utilities; we all have these kind of classes in our projects to factorize pure-technical stuff. Unsupervised usage may lead to serious performance issues. Never do anything more than once.

Is this micro-optimization a complete waste of time? Not at all. We’ve done a lot of interesting things: explore .NET internals, run profiling sessions & benchmarks. The final solution is still valid but just not so important in our context. But it could be useful in the future…


License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead
France France
Yet another proof of concept

Comments and Discussions

 
QuestionEasier to understand Pin
Tony van Roon-Werten6-Oct-14 21:52
Tony van Roon-Werten6-Oct-14 21:52 
AnswerRe: Easier to understand Pin
Cybermaxs7-Oct-14 2:39
Cybermaxs7-Oct-14 2:39 
QuestionConfusion in question Pin
irneb6-Oct-14 4:09
irneb6-Oct-14 4:09 
AnswerRe: Confusion in question Pin
Cybermaxs6-Oct-14 4:46
Cybermaxs6-Oct-14 4:46 
GeneralI'm confused... Pin
SledgeHammer013-Oct-14 21:07
SledgeHammer013-Oct-14 21:07 
GeneralRe: I'm confused... Pin
Cybermaxs4-Oct-14 1:04
Cybermaxs4-Oct-14 1:04 
GeneralRe: I'm confused... Pin
SledgeHammer014-Oct-14 6:07
SledgeHammer014-Oct-14 6:07 
GeneralRe: I'm confused... Pin
Cybermaxs4-Oct-14 9:32
Cybermaxs4-Oct-14 9:32 
It's totally true and I can only agree with you. A highly scalable application may even skip practices like TDD, avoid layers of abstraction, and use mainly static methods. But it's rarely a solution because we should keep our code easy to understand and to maintain. When choosing an Ioc, it's also important to compare features vs performances.

I have a great respect for this benchmark but it's just another context and the main test resolves 500.000 instances : with 50 000 instances maybe all Ioc take 0 sec (I guess not) but we don't know (linear response time or not). Of course, this kind of article is a very strong indication but I can't trust it without testing this part in my application. I even don't want to compare my Ioc to the others at this stage. I will only look for another container if I can prove that this one is a problem. Maybe I'm not enough pro-active but I prefer to inspect high CPU/memory conditions to diagnose a problem.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.