C# performance tips & tricks

John-DanielWeb Development12 Comments

up

At Raygun, we’re a pretty polyglot group of developers. Various parts of Raygun are written in different languages and frameworks – whatever is best for the job. At present we have:

Given the vast amount of C# and the explosive growth in data we’re dealing with, some optimisation work has been needed at various times. Most of the big gains come from really re-thinking a problem and approaching it from a whole new angle. Today however, I wanted to share some C# performance tips that have helped in my recent work. Some of these are fairly micro so don’t just charge out and employ everything here. With that, tip 1 is…

Every developer should use a Profiler

There are some great .NET profilers out there. I personally use the dotTrace profiler from the Jet Brains team. I know Jason on our team gets a lot of value from the Red Gate profiler also.

Every developer should have a profiler installed, and use it.

I can’t count the number of times that I’ve assumed the slow part of an application was in one area when in fact it was somewhere else completely. Profilers help with that. Furthermore, sometimes, it’s helped me find bugs – a part that was slow was only slow because it was doing something incorrectly (that wasn’t being picked up properly by a unit test).

This the first, and effectively mandatory, step of any optimisation work you’re going to be doing.

The higher the level, the slower the speed (usually)

This is just a smell that I’ve picked up on. The higher level abstraction you’re using, the slower it will often be. A common example here that I’ve found is using LINQ when you’re inside a busy part of code (perhaps inside a loop being called millions of times). LINQ is great for expressing something quickly that might otherwise take a bunch of lines of code, but you’re often leaving performance on the table.

Don’t get me wrong – LINQ is great for allowing you to crank out a working app. But in performance focused parts of your code base you can be giving away too much. Especially since it’s so easy to chain together so many operations.

The specific example I had, was where I was using a .SelectMany().Distinct().Count(). Given this was being called tens of millions of times (critical hot point found by my profiler) it was stacking up to a huge amount of the running time. I took another approach and reduced the execution time by several orders of magnitude.

Don’t under estimate Release builds vs. Debug builds

I’d been hacking away and was pretty happy with the performance I was getting. Then I realised I’d been doing all my tests inside Visual Studio (I often write my performance tests to run as unit tests also, so I can more easily run just the part I care about). We all know that release builds have optimisations enabled.

So I did a release build, called the methods I was testing from a console app.

I got a great turn around with this. My code had been optimised like crazy by me, so it really was time for some of the micro-optimisations that the .NET JIT compiler to shine. I gained about about an extra 30% performance with the optimisations enabled!

This reminds me of a story I read online a while back. An old game programming tale from the 90’s – back when memory limitations were super tight. Late the development cycle the team would ultimately run out of memory and start thinking about what had to be removed or downgraded to fit inside the tiny memory foot print available. The senior developer had expected this, based on his experience, and had allocated 1MB of memory with junk data at the very start of the project. He then saved the day and solved the problem by removing the 1MB of memory he’d allocated right at the start of the project! Knowing the team always ran out of space, by having the memory there to free gave the team what they needed and they shipped on time.

Why do I share this? It’s similar in performance land – get something running well enough in debug mode and you’re about to get some “free” performance in a release build. Good times.

Look at the bigger picture

There are some fantastic algorithms out there. Most you don’t need on a day to day, or even month to month basis. It is however worth knowing they exist. All too often I discover a much better approach to solving a problem once I do some research. A developer doing research before coding is about as likely as a developer doing proper analysis before writing code. We LOVE code and always want to dive right into the IDE.

Furthermore, often when looking at performance problems we focus too heavily on a single line or method. This can be a mistake – looking at the big picture can help you improve performance far more significantly by reducing the work that needs to be done.

I recommend reading resources like Clever Algorithms: http://www.cleveralgorithms.com/ It was certainly an eye opener to me on some of the more advanced algorithms out there.

Memory locality matters

Lets assume we have an array of arrays. Effectively it’s a table, 3000×3000 in size. We want to count how slots have a value greater than zero in them.

Question – which of these two is faster?

Answer? The first one. How much so? In my tests I got about an 8x performance improvement on this loop!

Notice the difference? It’s the order that we’re walking this array of arrays ([i][n] vs. [n][i]). Memory locality does indeed matter in .NET even though we’re well abstracted from managing memory ourselves.

In my case, this method was being called millions of times (hundreds of millions of times to be exact) and therefore any performance I could squeeze out of this resulted in a sizeable win. Again, thanks to my ever handy profiler for making sure I was focused on the right place!

In Summary

This has been a collection of just a few things I’ve found useful for picking up the performance of my .NET code. There’s obviously a lot of other optimisation tips and tricks and many I haven’t written about here. I welcome you sharing any you have in the comments 🙂

Also, hacker news comments here.

Thanks!

Next level software intelligence across your entire stack.

12 Comments on “C# performance tips & tricks”

  1. Mihail

    Excellent post! As a performance/memory profiler writer, I couldn’t agree more 😉

    I would like to stress once again that good algorithms and good locality of reference are of the utmost importance for good performance. Also, I would like to provide another POV about “The higher the level, the slower the speed” statement. First, LINQ has horrible performance. This is out of doubt. However, in my experience higher level code actually improves the performance. It is so, because higher level code means less code duplication and avoiding doing things which are not related to the particular tasks. Higher level code helps the compiler as well.

    Cheers

  2. Dom Finn

    Thanks for this. I can’t believe the difference on them two FOR loops. I’ll have to get checking my code. I don’t think I am clear on why them first one is faster. Is it the location of i as opposed n and so therefore the time it takes to reference them? I didn’t know you were guaranteed anything.

    The linq problem is the question of correctness / expressiveness over speed. I think it’s definitely right to go as back to basics as possible when speed is the key. I find people often alt+enter on for / for each loops using reshaper without even thinking about performance.

    1. PGladius

      The loop is faster because of the processor caching. If you access a variable inside an array, the processor fetches the entry into the processor cache. But not only the entry but also the next ones. So if you arrange your loop to access those entries already in the cache, the processor has no need to load those entries from the slow ram (compared to cache) thus increasing performance. Otherwise the processor needs to dump and reload cache rows, which costs some time.

  3. wydok

    What about:

    int len = _map.Length
    for (int i = 0; i < len ; ++i)
    {
    var mapi = map[i];

    for (int n = 0; n 0)
    {
    result++;
    }
    }
    }

  4. JH

    Late to this but wanted to add that following is even faster because optimizer can omit array bound checking for inner “for” loop because array “Length” is used.
    for (int i = 0; i < _map.Length; i++)
    {
    var arr = _map[i];
    for (int n = 0; n 0)
    {
    result++;
    }
    }
    }

  5. JH

    Late to this but following is even faster because optimizer will omit bound checking when array “Length” length property is used in “for” loop.

    for (int i = 0; i < _map.Length; i++)
    {
    var arr = _map[i];
    for (int n = 0; n 0)
    {
    result++;
    }
    }
    }

  6. Pingback: Unit testing patterns: common patterns to follow for error free applications

  7. Pingback: How Raygun increased throughput by 2,000% with .NET Core (over Node.js)

  8. Pingback: Moving from Node.js to .NET Core with Raygun and JavaScript Jabber [Podcast]

Leave a Reply

Your email address will not be published. Required fields are marked *