Performance in .NET Core

Performance in .NET Core



image



Hello! This article is a collection of Best Practices, which I and my colleagues have been using for a long time when working on different 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 do not want to steal someone else’s fame and copy someone else’s information, so I’ll just provide a link to a good article where the comparison is detailed .



On my own, I just want to present you my measurements and results, I added reverse loops to them for fans of 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 are almost identical. Here is what I managed to get in .NET Core 3.0:











We can conclude that looping a collection of type Array is faster, due to its internal optimizations and explicit allocation of the size of the collection. It is also worth remembering that a collection of type List has its advantages and you should use the desired collection depending on the necessary calculations. Even if you write the logic of working with cycles, do not forget that this is an ordinary loop and it is also subject to possible optimization of cycles. An article appeared on habr for a long time: https://habr.com/en/post/124910/ . It is still relevant and recommended for reading.



Throw



A year ago, I worked in a company on a legacy project, in that project it was within the normal framework to handle field validation through a try-catch-throw construct. I already understood that this was an unhealthy business logic for the project, so I tried not to use such a design if possible. But let's see what is the bad approach to handle errors with such a design. I wrote a little code in order to compare the two approaches and shot “benches” for each option.



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; }
      
      







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











Try catch complicates the understanding of the code and increases the execution time of your program. But if you need this construction, you should not insert those lines of code from which error handling is not expected - this will facilitate understanding of the code. In fact, it is not so much exception handling that is loading the system as throwing the errors themselves through the throw new Exception construct.



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



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



ToLower, ToLowerInvariant, ToUpper, ToUpperInvariant



Over his 5 years of experience working on the .NET platform, he met many projects that used string matching. I also saw the following picture: there was one Enterprise solution with many projects, each of which performed string comparisons in different ways. But what is worth using and how to unify it? In Richter’s CLR via C #, I read that ToUpperInvariant () is faster than ToLowerInvariant ().



Clipping from the book:







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% of the performance gain. Then, on coming to 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 for new measurements, and given that these two projects existed to process huge excel tables, the result was more than significant for the product.



I also present you 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 in 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(); }
      
      



; 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(); }















In .NET Core 3.0, the gain for each of these methods is ~ x2 and balances the implementations among themselves.











Tier compilation



In my last article I briefly described this functionality, I would like to correct and supplement my words. Layered compilation speeds up the launch time of your solution, but you sacrifice parts of your code to be compiled into a more optimized version in the background, which can lead to small overhead. With the advent of NET Core 3.0, the build time of projects with tier compilation enabled decreased and fixed bugs related to this technology. Previously, this technology led to errors in the first requests in ASP.NET Core and to freezes during the first build in multilevel compilation mode. Currently, it is enabled by default in .NET Core 3.0, but you can disable it as desired. If you are in the position of team-lead, senior, middle or you are the head of the department, you must understand that the rapid development of the project increases the value of the team and this technology will allow you to save both the developers time and the time of the project.



.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:









Conclusion



When writing code, you should pay attention to various aspects of your project and use the functions 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.



Github link



All Articles