DEV Community

Ilya Ermoshin
Ilya Ermoshin

Posted on

Performance in .NET

Hello everyone Today we are going to talk about the performance in .NET. In this article, I would like to break down how you can improve performance and readability of the code in just two lines of code.

Information about the machine on which the calculations were performed:

Low Power Mode = true
BenchmarkDotNet=v0.13.5, OS=macOS Ventura 13.2.1 (22D68) [Darwin 22.3.0]
Apple M2 Pro, 1 CPU, 12 logical and 12 physical cores
.NET SDK=7.0.102
[Host] : .NET 7.0.2 (7.0.222.60605), Arm64 RyuJIT AdvSIMD
.NET 7.0 : .NET 7.0.2 (7.0.222.60605), Arm64 RyuJIT AdvSIMD

Source Code: https://github.com/Sin333/PerformanceNETCore

Collections
The choice of collections is always up to developers, but often due to low skill level or low code quality requirements, they choose the most versatile collection (List) regardless of all its features and disadvantages. From time to time in different companies I meet a misunderstanding when use an array for the data that is needed only to take data from the database and give it back to the client. And every time you need to explain why do you need to use IReadOnlyCollection, ImmutableCollection collections.

Collection 1Collection 2

Wow, the ToArray function works many times faster. Surprisingly, it turns out that using the right collections can not only improve the readability of the code, but also give a good performance boost :)

I had one story at work where it was necessary to solve the problem with exporting excel files. The problem was that the file was generated longer than the waiting time on the client, a quick solution was needed within one day that would solve the problem as quickly as possible. The longest block of code was in a LINQ request. Everything was quite simple there, data is taken from the database, grouped, sorted and materialized into a List collection and comeback to client-side. As soon as I just changed the functions ToList to ToArray, we were immediately able to reduce the execution time of the function by ~30%. Well, there is no magic here, I just used the right collection for a specific task (ToListAsync() -> ToArrayAsync()).

  • If you only need to get data and send the collection without any changes, use the collections that are most suitable for immutable data such as: Array, IReadOnlyCollection, ImmutableCollection etc.
  • Please do not recreate a collection if you don't need it. Just look at this weird .ToList().ToArray() construct.
  • Imagine you have a dataset from which you need to take elements often and compare them with other data, Dictionary will help you with this better than any other collection. Some of you will ask why? Everything is quite simple here, you understand the search by key in Dictionary - O (1) thus obtaining any elements by key for you will be as fast as possible.

Cycles
We can talk a lot about optimizations, but let's look at how it works that all developers use, namely the automation of algorithms. The main automation that a developer resorts to is to reduce the number of lines of code and minimize code duplication. And the very first construct in the code suitable for this is Loops. They allow you to repeat many of the same operations with just a couple of lines of code. The cycles are different, and it would be good to understand how they differ. Let's take performance measurements and see if there are any differences and how they work.

Collection 11000000 collection10000000 collection

If we look at the results, the first thing we see is that iterating over a List is slower than iterating over an Array. Why? Logically, iterating over an Array is always more efficient than iterating over a List, since a List is a wrapper around an array. Also following the logic, for is always faster than foreach, since foreach does extra checks. The point is that for iterating over an array, foreach does not use the IEnumerable implementation. In this particular case, the most optimized iteration over the index is performed, without checking for array out-of-bounds, since the foreach construct does not operate on indexes, so the developer does not have the opportunity to create an ArgumentOutOfRangeException error in the code.

  1. for
    • Array – base iteration over index
    • List – base iteration over index
  2. foreach
    • Array – base iteration over index 😺
    • List – IEnumerable.GetEnumerator() & IEnumerable.MoveNext() πŸ˜ΎπŸ’”

Keep in mind that the List collection has its advantages and you should use the correct collection depending on the calculation you need. Even if you are writing logic to work with loops, you must not forget that this is a regular loop and is also subject to possible loop optimization. One possible optimization that the compiler can make is to unroll the loop, if the size of the collection is known in advance, then the compiler will go through the entire loop for X lines of code without going through the collection. But you can also reduce the number of iterations in the loop by using the two continue & break operators.

Throw
A few years ago I worked in a company on a legacy project, in that project they used to process field validation through a try-catch-throw construct.
Even then I had a feeling that this was an nonoptimal realization of validation, so I tried not to use such a construction whenever possible. But let's see why the approach to handle errors with such a construction is bad. I wrote a little code to compare the two approaches and benchmarked each one.

Throw 1Throw2

As you can see difference greater than 1000x πŸ˜‰

Try catch makes the code harder to understand and increases the execution time of your program. But if you need this construct, you should not insert those lines of code from which error handling is not expected - this will make the code easier to understand. In fact, it’s not so much exception handling that loads the system, but throwing the errors themselves through the throw new Exception construct.

Throwing exceptions is slower than any class that will collect an error in the required format. If you are processing a form or some data, and you clearly know what the error should be, why not process it?

You should not write a throw new Exception() construct unless this situation is exceptional. Handling and throwing an exception is very expensive!!!

String compare
In my 9 years of experience on the .NET platform, I have never worked in a project without using string matching. And in each micro-service team did string comparison in different ways. But what should be used and how to unify it? In the book CLR via C# Richter, I read information that the ToUpperInvariant() method is faster than ToLowerInvariant().

Extract from the book:
Image description

Of course, I didn't believe it and decided to run tests on the .NET Framework, and the result shocked me - a performance increase of more than 15%. Then, when I came to work the next morning, I shared my experience with the team and we decided to try it on our project. Surprisingly, after testing on our project, we received a significant performance boost. We received the strongest increase in the microservice responsible for generating Excel files.

But actually now I recommend you use function:

string.Compare(strA, strB, StringComparison.OrdinalIgnoreCase)
Enter fullscreen mode Exit fullscreen mode

It's better solution for string compares.
Image descriptionString compare

Good Practices and Optimization Tips

  1. Remember: if you are still using the .NET Framework it is never too late to upgrade your software to .NET Core or .NET 5 and up. This is the best way to optimize your software not only in terms of system speed, but also greatly improve the project compilation speed, reduce memory consumption and give the opportunity to work on your project not only as a developer with Windows OS. Optimization, with each new version, the optimization of the core libraries Collection/Struct/Stream/String/Regex and much more is improved. If you're moving from .NET Framework to .NET Core, you'll get a big performance boost out of the box. I am attaching a link to moving bing from NET Framework to .NET 5: https://devblogs.microsoft.com/dotnet/migration-of-bings-workflow-engine-to-net-5/ and attaching a link to moving bin from .NET 5 to .NET 7: https://devblogs.microsoft.com/dotnet/dotnet-performance-delivers-again-for-bing-from-dotnet-5-to-dotnet-7/
  2. Use C# keywords like: static, const, readonly, sealed, abstract, etc, where they make sense. You may think - how is this connected to optimization? The fact is that the more detailed you describe your system to the compiler, the more optimal code it will be able to generate. Give the compiler and virtual machine a chance! As a bonus, you'll find many misuse errors in your code at compile time. General rule: the more clearly the system is described, the better the result you get.
  3. If your project has regular expressions, then there is a high probability that they may not be written optimally, I strongly advise you to read my previous article on regular expressions to better understand how they work and how you can speed up your patterns: https://dev.to/sineni/regex-for-lazy-developers-cg1

End..?
When writing code, you should pay attention to different aspects of your project and use the capabilities of your programming language and platform to achieve the best result. It is important to remember that improving the performance of your system not only helps you understand the code better, but also helps you save money on resources in cloud systems such as Azure, AWS, etc. These are far from all the optimizations that I would like to share with you, but let it be good short article. If you have additional tips or history for improving code quality and code performance after reading the article, I will be glad to hear your comments! Thank you very much for reading this article to the end, I sincerely hope that it was useful to you

Source Code: https://github.com/Sin333/PerformanceNETCore

Top comments (0)