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.
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 inDictionary
- 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.
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.
-
for
-
Array
β base iteration over index -
List
β base iteration over index
-
-
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.
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()
.
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)
It's better solution for string compares.
Good Practices and Optimization Tips
- 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/ - 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.
- 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)