Performance in .NET Core

Performance in .NET Core

Performance in .NET Core

Hi all! This article is a collection of Best Practices that my colleagues and I have been using for a long time when working on various projects.

Information about the machine on which the calculations were performed:BenchmarkDotNet=v0.11.5, OS=Windows 10.0.18362
Intel Core i5-8250U CPU 1.60GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.0.100
[Host]: .NET Core 2.2.7 (CoreCLR 4.6.28008.02, CoreFX 4.6.28008.03), 64bit RyuJIT
Core: .NET Core 2.2.7 (CoreCLR 4.6.28008.02, CoreFX 4.6.28008.03), 64bit RyuJIT
[Host]: .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), 64bit RyuJIT
Core: .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), 64bit RyuJIT

Job=Core Runtime=Core

ToList vs ToArray and Cycles


I planned to prepare this information with the release of .NET Core 3.0, but they got ahead of me, I don’t want to steal someone else’s glory and copy someone else’s information, so I’ll just point out link to a good article where the comparison is detailed.

From myself, I just want to present you my measurements and results, I added reverse loops to them for those who like the “C ++ style” of writing loops.

code:

public class Bench
    {
        private List<int> _list;
        private int[] _array;

        [Params(100000, 10000000)] public int N;

        [GlobalSetup]
        public void Setup()
        {
            const int MIN = 1;
            const int MAX = 10;
            Random random = new Random();
            _list = Enumerable.Repeat(0, N).Select(i => random.Next(MIN, MAX)).ToList();
            _array = _list.ToArray();
        }

        [Benchmark]
        public int ForList()
        {
            int total = 0;
            for (int i = 0; i < _list.Count; i++)
            {
                total += _list[i];
            }

            return total;
        }
        
        [Benchmark]
        public int ForListFromEnd()
        {
            int total = 0;t
            for (int i = _list.Count-1; i > 0; i--)
            {
                total += _list[i];
            }

            return total;
        }

        [Benchmark]
        public int ForeachList()
        {
            int total = 0;
            foreach (int i in _list)
            {
                total += i;
            }

            return total;
        }

        [Benchmark]
        public int ForeachArray()
        {
            int total = 0;
            foreach (int i in _array)
            {
                total += i;
            }

            return total;
        }

        [Benchmark]
        public int ForArray()
        {
            int total = 0;
            for (int i = 0; i < _array.Length; i++)
            {
                total += _array[i];
            }

            return total;
        }
        
        [Benchmark]
        public int ForArrayFromEnd()
        {
            int total = 0;
            for (int i = _array.Length-1; i > 0; i--)
            {
                total += _array[i];
            }

            return total;
        }
    }

The performance in .NET Core 2.2 and 3.0 is almost identical. Here is what I was able to get in .NET Core 3.0:

Performance in .NET Core

Performance in .NET Core

We can conclude that looping through an Array collection is faster due to its internal optimizations and explicit allocation of the collection size. It's also worth remembering that a List collection has its advantages and you should use the right collection depending on the calculations you need. Even if you write logic for working with loops, you should not forget that this is an ordinary loop and it is also subject to possible loop optimization. An article was published on habr quite a long time ago: https://habr.com/ru/post/124910/. It is still relevant and recommended reading.

Throw

A year ago, I worked in a company on a legacy project, in that project it was normal to process field validation through a try-catch-throw construct. Even then I understood that this was an unhealthy business logic of the project, 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.

code:

        public bool ContainsHash()
        {
            bool result = false;
            foreach (var file in _files)
            {
                var extension = Path.GetExtension(file);
                if (_hash.Contains(extension))
                    result = true;
            }

            return result;
        }

        public bool ContainsHashTryCatch()
        {
            bool result = false;
            try
            {
                foreach (var file in _files)
                {
                    var extension = Path.GetExtension(file);
                    if (_hash.Contains(extension))
                        result = true;
                }
                
                if(!result) 
                    throw new Exception("false");
            }
            catch (Exception e)
            {
                result = false;
            }

            return result;
        }

Results in .NET Core 3.0 and Core 2.2 have a similar result (.NET Core 3.0):

Performance in .NET Core

Performance in .NET Core

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 obviously 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!!!

ToLower, ToLowerInvariant, ToUpper, ToUpperInvariant

In my 5 years of experience on the .NET platform, I have come across a lot of projects that used string matching. I also saw the following picture: there was one Enterprise solution with many projects, each of which performed 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:

Performance in .NET Core

Of course, I did not believe it and decided to conduct some tests then on the .NET Framework, and the result shocked me - more than 15% performance increase. Then, upon arriving at work the next morning, I showed these measurements to my superiors and gave them access to the source. After that, 2 out of 14 projects were changed to new measurements, and given that these two projects existed to process huge excel spreadsheets, the result was more than significant for the product.

I also present you with measurements for different versions of .NET Core, so that each of you can make a choice in the direction of the most optimal solution. And I just want to add that at the company where I work, we use ToUpper() to compare strings.

code:

public const string defaultString =  "VXTDuob5YhummuDq1PPXOHE4PbrRjYfBjcHdFs8UcKSAHOCGievbUItWhU3ovCmRALgdZUG1CB0sQ4iMj8Z1ZfkML2owvfkOKxBCoFUAN4VLd4I8ietmlsS5PtdQEn6zEgy1uCVZXiXuubd0xM5ONVZBqDu6nOVq1GQloEjeRN8jXrj0MVUexB9aIECs7caKGddpuut3";

        [Benchmark]
        public bool ToLower()
        {
            return defaultString.ToLower() == defaultString.ToLower();
        }

        [Benchmark]
        public bool ToLowerInvariant()
        {
            return defaultString.ToLowerInvariant() == defaultString.ToLowerInvariant();
        }

        [Benchmark]
        public bool ToUpper()
        {
            return defaultString.ToUpper() == defaultString.ToUpper();
        }

        [Benchmark]
        public bool ToUpperInvariant()
        {
            return defaultString.ToUpperInvariant() == defaultString.ToUpperInvariant();
        }

Performance in .NET Core

Performance in .NET Core

In .NET Core 3.0, the increment for each of these methods is ~x2 and balances the implementations with each other.

Performance in .NET Core

Performance in .NET Core

Tier Compilation

In my last article, I described this functionality briefly, I would like to correct and supplement my words. Layered compilation speeds up the startup time of your solution, but you sacrifice that parts of your code will be compiled to a more optimized version in the background, which can result in a small overhead. With the advent of NET Core 3.0, the build time for projects with tier compilation enabled has decreased and bugs associated with this technology have been fixed. Previously, this technology caused first request errors in ASP.NET Core and hangs on first build in layered compilation mode. It is currently enabled by default in .NET Core 3.0, but you can disable it if you wish. If you are in the position of team-lead, senior, middle or you are the head of the department, you should understand that the rapid development of the project increases the value of the team and this technology will allow you to save time for both developers and the time of the project itself.

.NET level up

Upgrade your .NET Framework / .NET Core. Often, each new version gives an additional performance boost and adds new features.

But what exactly are the benefits? Let's look at some of them:

  • .NET Core 3.0 introduced R2R images, which will reduce the startup time of .NET Core applications.
  • With version 2.2, Tier Compilation has appeared, thanks to which programmers will spend less time launching a project.
  • Support for new .NET Standards.
  • Support for a new version of the programming language.
  • 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. For example, I am attaching a link to some of the optimizations that were added in .NET Core 3.0: https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-core-3-0/

Performance in .NET Core

Conclusion

When writing code, you should pay attention to different aspects of your project and use the features of your programming language and platform to achieve the best result. I will be glad if you share your knowledge related to optimization in .NET.

Link to github

Source: habr.com

Add a comment