Performance in .NET Core

Salut tuturor! Acest articol este o colecție de Best Practices pe care eu și colegii mei le aplicăm de mult timp în diferite proiecte.
Informații despre mașina pe care au fost efectuate calculările:BenchmarkDotNet=v0.11.5, OS=Windows 10.0.18362
Intel Core i5-8250U CPU 1.60GHz (Kaby Lake R), 1 CPU, 8 nuclee logice și 4 nuclee fizice
.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 și Cicli
Această informație am planificat-o pentru lansarea .NET Core 3.0, dar m-au depășit, nu vreau să fur altcuiva gloria și să copiez informația altora, așa că voi indica .
Din partea mea, vreau să vă prezint măsurătorile și rezultatele mele, am adăugat cicli inversi pentru cei care preferă stilul „C++” de scriere a cicliilor.
Cod:
public class Bench
{
private List _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 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 0; i--)
{
total += _array[i];
}
return total;
}
}
Viteza de lucru în .NET Core 2.2 și 3.0 este aproape identică. Iată ce am reușit să obțin în .NET Core 3.0:


Putem concluziona că procesarea ciclică a unei colecții de tip Array este mai rapidă, datorită optimizărilor interne și alocării explicite a dimensiunii colecției. De asemenea, trebuie să ne amintim că o colecție de tip List are propriile sale avantaje și ar trebui să folosiți colecția potrivită în funcție de calculele necesare. Chiar dacă scrieți logica de lucru cu bucle, nu trebuie să uitați că este o simplă buclă și și ea este supusă posibilelor optimizări. Pe habr a apărut de mult timp un articol: . Este încă relevant și recomandat pentru citire.
Aruncă
Acum un an, lucram la o companie la un proiect legacy, iar în acel proiect era normal să gestionăm validarea câmpurilor prin construcția try-catch-throw. Atunci deja înțelegeam că aceasta este o logică de afaceri nesănătoasă pentru proiect, așa că, pe cât posibil, încercam să nu folosesc o astfel de construcție. Dar să vedem de ce abordarea de a gestiona erorile în această construcție este proastă. Am scris un cod mic pentru a compara cele două abordări și am realizat „benchmark-uri” pentru fiecare variantă.
Cod:
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;
}Rezultatele din .NET Core 3.0 și Core 2.2 au rezultate similare (.NET Core 3.0):


Try catch complică înțelegerea codului și crește timpul de execuție al programului dumneavoastră. Dar dacă aveți nevoie de această construcție, nu trebuie să inserați acele linii de cod pentru care nu se așteaptă gestionarea erorilor — acest lucru va ușura înțelegerea codului. De fapt, sistemul nu este atât de mult afectat de gestionarea excepțiilor, cât de aruncarea propriilor erori prin construcția throw new Exception.
Aruncarea excepțiilor funcționează mai lent decât oricare altă clasă care va colecta eroarea într-un format adecvat. Dacă gestionați un formular sau unele date și știți clar ce eroare ar trebui să fie, de ce să nu le gestionați?
Nu este nevoie să scrii construcția throw new Exception() dacă această situație nu este excepțională. Gestionarea și aruncarea excepțiilor costă foarte mult!!!
ToLower, ToLowerInvariant, ToUpper, ToUpperInvariant
În cei 5 ani de experiență în lucru pe platforma .NET am întâlnit multe proiecte care foloseau compararea de șiruri. De asemenea, am văzut următoarea situație: existau soluții Enterprise cu numeroase proiecte, fiecare dintre ele realizând comparații de șiruri în mod diferit. Dar ce ar trebui să folosim și cum să unificăm asta? În cartea CLR via C# de Richter am citit informația că metoda ToUpperInvariant() funcționează mai repede decât ToLowerInvariant().
Extras din carte:

Desigur, nu am crezut și am decis să fac unele teste pe atunci pe .NET Framework și rezultatul m-a șocat — o creștere de peste 15% a performanței. A doua zi dimineața am arătat aceste măsurători șefilor mei și le-am oferit acces la sursa codului. După aceea, 2 din 14 proiecte au fost modificate conform noilor măsurători, iar având în vedere că aceste două proiecte existau pentru a procesa tabele enorme Excel, rezultatul a fost mai mult decât semnificativ pentru produs.
De asemenea, vă prezint măsurători pentru diferite versiuni .NET Core, astfel încât fiecare dintre voi să poată face alegerea către soluția cea mai optimă. Și vreau doar să adaug că în compania în care lucrez, folosim ToUpper() pentru compararea șirurilor.
Cod:
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();
}


În .NET Core 3.0, creșterea pentru fiecare dintre aceste metode este ~x2 și echilibrează implementările între ele.


Compilare pe niveluri
În articolul meu anterior, am descris pe scurt această funcționalitate și aș dori să corectez și să completez ceea ce am spus. Compilarea multi-nivel reduce timpul de pornire al soluției dumneavoastră, dar în schimb, renunțați la faptul că părți ale codului dumneavoastră vor fi compilate într-o versiune mai optimizată în fundal, ceea ce poate duce la costuri suplimentare minime. Odată cu apariția .NET Core 3.0, timpul de compilare al proiectelor cu compilarea tier a fost redus, și au fost corectate bug-uri legate de această tehnologie. În trecut, această tehnologie cauza erori la primele solicitări în ASP.NET Core și blocarea la prima compilare în modul de compilare multi-nivel. În prezent, în .NET Core 3.0, este activată implicit, dar o puteți dezactiva dacă doriți. Dacă ocupați o funcție de team-leader, senior, middle sau sunteți șef de departament, trebuie să înțelegeți că dezvoltarea rapidă a proiectului crește valoarea echipei, iar această tehnologie vă va permite să economisiți timp atât pentru dezvoltatori, cât și pentru timpul total al proiectului.
.NET de nivel superior
Actualizați versiunea .NET Framework / .NET Core. Adesea, fiecare nouă versiune aduce un plus de performanță și adaugă funcționalități noi.
Dar care sunt avantajele specifice? Să analizăm câteva dintre ele:
- În .NET Core 3.0, au apărut imaginile R2R, care vor reduce timpul de pornire al aplicațiilor .NET Core.
- De la versiunea 2.2, a fost introdusă Compilarea Tier, care va permite programatorilor să petreacă mai puțin timp la pornirea proiectului.
- Sprijin pentru noi standarde .NET Standard.
- Sprijin pentru noua versiune a limbajului de programare.
- Optimizare, cu fiecare nouă versiune se îmbunătățește optimizarea bibliotecilor de bază Collection/Struct/Stream/String/Regex și multe altele. Dacă treceți de la .NET Framework la .NET Core, veți obține un mare câștig în performanță din cutie. Ca exemplu, atașez un link cu o parte din optimizările care au fost adăugate în .NET Core 3.0:

Concluzie
Când scrieți cod, este important să acordați atenție diferitelor aspecte ale proiectului dumneavoastră și să folosiți funcțiile limbajului de programare și platformei pentru a obține cele mai bune rezultate. Aș fi bucuros dacă ați împărtăși cunoștințele dumneavoastră legate de optimizare în .NET.
Sursa: habr.com
