Prestazioni in .NET Core

Ciao a tutti! Questo articolo è una raccolta delle migliori pratiche che io e i miei colleghi seguiamo da molto tempo nei vari progetti.
Informazioni sulla macchina utilizzata per i calcoli:BenchmarkDotNet=v0.11.5, OS=Windows 10.0.18362
Intel Core i5-8250U CPU 1.60GHz (Kaby Lake R), 1 CPU, 8 core logici e 4 core fisici
.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 e Cicli
Avevo pianificato di preparare queste informazioni con il rilascio di .NET Core 3.0, ma qualcuno mi ha superato. Non voglio rubare la gloria altrui e copiare informazioni, quindi indicherò semplicemente .
Da parte mia, voglio semplicemente presentarvi le mie misurazioni e risultati; ho aggiunto cicli inversi per gli amanti dello stile di scrittura “C++”.
Codice:
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;
}
}
La velocità di esecuzione in .NET Core 2.2 e 3.0 è quasi identica. Ecco cosa sono riuscito a ottenere in .NET Core 3.0:


Possiamo concludere che la gestione ciclica delle collezioni di tipo Array è più veloce grazie alle sue ottimizzazioni interne e all'allocazione esplicita della dimensione della collezione. È importante ricordare che anche le collezioni di tipo List hanno i loro vantaggi, e dovresti usare la collezione appropriata a seconda dei calcoli richiesti. Anche se stai scrivendo la logica per lavorare con i cicli, non dimenticare che si tratta di un normale loop, e può anch'esso essere ottimizzato. Su Habr è uscito tempo fa un articolo: È ancora pertinente e consigliato per la lettura.
Throw
Un anno fa lavoravo in un'azienda su un progetto legacy, in cui si gestiva la validazione dei campi tramite la costruzione try-catch-throw. Già allora capivo che questa logica di business non fosse sana, quindi, per quanto possibile, cercavo di evitarla. Ma vediamo perché è inefficace gestire gli errori in questo modo. Ho scritto un piccolo codice per confrontare i due approcci e ho fatto alcuni benchmark per ciascuna variante.
Codice:
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;
}I risultati in .NET Core 3.0 e Core 2.2 sono simili ( .NET Core 3.0):


Try-catch complica la comprensione del codice e aumenta il tempo di esecuzione del programma. Ma se hai bisogno di questa struttura, non dovresti inserire quelle righe di codice per cui non ci si aspetta una gestione degli errori: ciò faciliterà la comprensione del codice. In realtà, carica il sistema non tanto la gestione delle eccezioni quanto piuttosto il lancio delle stesse eccezioni attraverso la struttura throw new Exception.
Lanciando eccezioni si lavora più lentamente rispetto a una classe che raccoglie l'errore nel formato richiesto. Se stai elaborando un modulo o dei dati e sai esattamente quale deve essere l'errore, perché non gestirlo?
Non dovresti scrivere la struttura throw new Exception() se questa situazione non è eccezionale. La gestione e il lancio di un'eccezione sono estremamente costosi!!!
ToLower, ToLowerInvariant, ToUpper, ToUpperInvariant
Nel mio percorso di 5 anni sulla piattaforma .NET ho incontrato numerosi progetti che utilizzavano il confronto delle stringhe. Ho anche osservato una situazione in cui esisteva una soluzione Enterprise con molti progetti, ognuno dei quali eseguiva il confronto delle stringhe in modi diversi. Ma cosa dovremmo usare e come possiamo unificare queste pratiche? Nel libro CLR via C# di Richter, ho trovato informazioni su come il metodo ToUpperInvariant() funzioni più velocemente di ToLowerInvariant().
Estratto dal libro:

Naturalmente non ci credevo e decisi di eseguire alcuni test, all'epoca con .NET Framework, e i risultati mi hanno scioccato: oltre il 15% di guadagno in termini di prestazioni. Il giorno dopo, quando sono tornato al lavoro, ho mostrato i dati delle misurazioni al mio superiore e gli ho fornito accesso al codice sorgente. Successivamente, 2 progetti su 14 sono stati modificati per adeguarsi ai nuovi risultati, e considerando che questi due progetti servivano a gestire enormi tabelle Excel, il risultato è stato estremamente significativo per il prodotto.
Presento anche le misurazioni per le diverse versioni di .NET Core, in modo che ognuno di voi possa scegliere la soluzione più ottimale. Vorrei solo aggiungere che presso l'azienda in cui lavoro utilizziamo ToUpper() per il confronto delle stringhe.
Codice:
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, l'incremento per ciascuno di questi metodi è ~x2 e bilancia le implementazioni tra loro.


Compilazione Tier
Nel mio articolo precedente, ho descritto brevemente questa funzionalità e vorrei chiarire e integrare le mie parole. La compilazione a più livelli accelera il tempo di avvio della vostra soluzione, ma comporta il sacrificio del fatto che alcune parti del vostro codice verranno compilate in una versione più ottimizzata in background, il che può portare a lievi costi aggiuntivi. Con l'arrivo di .NET Core 3.0, è stato ridotto il tempo di costruzione dei progetti con la compilazione a livelli attivata e sono stati risolti i bug legati a questa tecnologia. In passato, questa tecnologia causava errori nelle prime richieste in ASP.NET Core e bloccava la prima compilazione in modalità di compilazione a più livelli. Attualmente, in .NET Core 3.0 è attivata per impostazione predefinita, ma potete disabilitarla se lo desiderate. Se ricoprite il ruolo di team leader, senior, middle o se siete responsabili di un dipartimento, dovete comprendere che uno sviluppo rapido del progetto aumenta il valore del team e questa tecnologia vi consentirà di risparmiare tempo sia per gli sviluppatori che per il tempo complessivo del progetto.
.NET level up
Aggiornate la versione del vostro .NET Framework / .NET Core. Spesso, ogni nuova versione offre un incremento delle prestazioni e introduce nuove funzionalità.
Ma quali sono esattamente i vantaggi? Esaminiamo alcuni di essi:
- In .NET Core 3.0 sono stati introdotti i profili R2R, che riducono il tempo di avvio delle applicazioni .NET Core.
- Con la versione 2.2 è arrivata la compilazione a livelli, permettendo ai programmatori di risparmiare tempo all'avvio del progetto.
- Supporto per i nuovi standard .NET Standard.
- Supporto per la nuova versione del linguaggio di programmazione.
- Ottimizzazione, con ogni nuova versione migliorano le ottimizzazioni delle librerie di base Collection/Struct/Stream/String/Regex e molto altro. Se passi da .NET Framework a .NET Core, otterrai un notevole aumento delle prestazioni fin da subito. Per esempio, allego un link ad alcune delle ottimizzazioni introdotte in .NET Core 3.0:

Conclusione
Quando scrivi codice, è importante prestare attenzione a vari aspetti del tuo progetto e utilizzare le funzionalità del linguaggio di programmazione e della piattaforma per ottenere i migliori risultati. Sarei felice se potessi condividere le tue conoscenze relative all'ottimizzazione in .NET.
Fonte: habr.com
