Prestazioni in .NET Core

Prestazioni in .NET Core

Prestazioni in .NET Core

Ciao a tutti! Questo articolo è una raccolta di migliori pratiche che io e i miei colleghi utilizziamo da molto tempo quando lavoriamo su diversi progetti.

Informazioni sulla macchina su cui sono stati eseguiti i calcoli:BenchmarkDotNet=v0.11.5, sistema operativo=Windows 10.0.18362
Intel Core i5-8250U CPU 1.60 GHz (Kaby Lake R), 1 CPU, 8 core logici e 4 core fisici
SDK .NET Core=3.0.100
[Host]: .NET Core 2.2.7 (CoreCLR 4.6.28008.02, CoreFX 4.6.28008.03), RyuJIT a 64 bit
Nucleo: .NET Core 2.2.7 (CoreCLR 4.6.28008.02, CoreFX 4.6.28008.03), RyuJIT a 64 bit
[Host]: .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), RyuJIT a 64 bit
Nucleo: .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), RyuJIT a 64 bit

Lavoro=Core Runtime=Core

ToList vs ToArray e Cicli


Avevo programmato di preparare queste informazioni con il rilascio di .NET Core 3.0, ma mi hanno preceduto, non voglio rubare la gloria di qualcun altro e copiare le informazioni di altre persone, quindi mi limiterò a sottolineare collegamento a un buon articolo in cui il confronto è descritto in dettaglio.

Per conto mio, voglio solo presentarvi le mie misurazioni e i risultati; ho aggiunto dei loop inversi per gli amanti dello “stile C++” di scrittura dei loop.

Codice:

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

Le velocità delle prestazioni in .NET Core 2.2 e 3.0 sono quasi identiche. Ecco cosa sono riuscito a ottenere in .NET Core 3.0:

Prestazioni in .NET Core

Prestazioni in .NET Core

Possiamo concludere che l'elaborazione iterativa di una raccolta Array è più veloce grazie alle ottimizzazioni interne e all'allocazione esplicita delle dimensioni della raccolta. Vale anche la pena ricordare che una raccolta List ha i suoi vantaggi e dovresti utilizzare la raccolta giusta a seconda dei calcoli richiesti. Anche se scrivi la logica per lavorare con i loop, non dimenticare che si tratta di un loop normale ed è anche soggetto a una possibile ottimizzazione del loop. Un articolo è stato pubblicato su habr parecchio tempo fa: https://habr.com/ru/post/124910/. Resta comunque una lettura attuale e consigliata.

Buttare

Un anno fa, ho lavorato in un'azienda su un progetto legacy, in quel progetto era normale elaborare la validazione sul campo attraverso un costrutto try-catch-throw. Già allora avevo capito che questa era una logica aziendale malsana per il progetto, quindi, quando possibile, ho cercato di non utilizzare un design del genere. Ma vediamo perché l’approccio alla gestione degli errori con una tale costruzione è negativo. Ho scritto un piccolo codice per confrontare i due approcci e creato parametri di riferimento per ciascuna opzione.

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 hanno un risultato simile (.NET Core 3.0):

Prestazioni in .NET Core

Prestazioni in .NET Core

Try catch rende il codice più difficile da comprendere e aumenta il tempo di esecuzione del programma. Ma se hai bisogno di questa costruzione, non dovresti inserire quelle righe di codice che non dovrebbero gestire errori: questo renderà il codice più facile da capire. In effetti, non è tanto la gestione delle eccezioni a caricare il sistema, quanto piuttosto la generazione degli errori stessi attraverso il costrutto launch new Exception.

La generazione di eccezioni è più lenta rispetto ad alcune classi che raccoglieranno l'errore nel formato richiesto. Se stai elaborando un modulo o alcuni dati e sai chiaramente quale dovrebbe essere l'errore, perché non elaborarlo?

Non dovresti scrivere un costrutto launch new Exception() se questa situazione non è eccezionale. Gestire e lanciare un'eccezione è molto costoso!!!

ToLower, ToLowerInvariant, ToUpper, ToUpperInvariant

Nel corso dei miei 5 anni di esperienza di lavoro sulla piattaforma .NET, mi sono imbattuto in molti progetti che utilizzavano la corrispondenza delle stringhe. Ho visto anche la seguente immagine: esisteva una soluzione Enterprise con molti progetti, ognuno dei quali eseguiva confronti di stringhe in modo diverso. Ma cosa dovrebbe essere utilizzato e come unificarlo? Nel libro CLR via C# di Richter ho letto informazioni secondo cui il metodo ToUpperInvariant() è più veloce di ToLowerInvariant().

Estratto dal libro:

Prestazioni in .NET Core

Ovviamente non ci credevo e ho deciso di eseguire alcuni test su .NET Framework e il risultato mi ha scioccato: un aumento delle prestazioni di oltre il 15%. Poi, la mattina dopo, quando sono arrivato al lavoro, ho mostrato queste misurazioni ai miei superiori e ho dato loro accesso al codice sorgente. Successivamente, 2 progetti su 14 sono stati modificati per accogliere le nuove misurazioni e, considerando che questi due progetti esistevano per elaborare enormi tabelle Excel, il risultato è stato più che significativo per il prodotto.

Presento anche misurazioni per diverse versioni di .NET Core, in modo che ognuno di voi possa fare una scelta verso la soluzione ottimale. E voglio solo aggiungere che nell'azienda in cui lavoro usiamo ToUpper() per confrontare le 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();
        }

Prestazioni in .NET Core

Prestazioni in .NET Core

In .NET Core 3.0 l'aumento per ognuno di questi metodi è ~x2 e bilancia le implementazioni tra loro.

Prestazioni in .NET Core

Prestazioni in .NET Core

Compilazione dei livelli

Nel mio ultimo articolo ho descritto brevemente questa funzionalità, vorrei correggere e integrare le mie parole. La compilazione multilivello accelera il tempo di avvio della tua soluzione, ma sacrifichi che parti del tuo codice verranno compilate in una versione più ottimizzata in background, il che può introdurre un piccolo sovraccarico. Con l'avvento di NET Core 3.0, il tempo di compilazione per i progetti con compilazione a livelli abilitata è diminuito e i bug associati a questa tecnologia sono stati risolti. In precedenza, questa tecnologia causava errori nelle prime richieste in ASP.NET Core e si blocca durante la prima compilazione in modalità di compilazione multilivello. Attualmente è abilitato per impostazione predefinita in .NET Core 3.0, ma puoi disabilitarlo se lo desideri. Se ricopri la posizione di team leader, senior, middle o sei a capo di un dipartimento, allora devi capire che lo sviluppo rapido del progetto aumenta il valore del team e questa tecnologia ti consentirà di risparmiare tempo per entrambi gli sviluppatori e il momento del progetto stesso.

.NET salire di livello

Aggiorna la versione di .NET Framework/.NET Core. Spesso, ogni nuova versione fornisce ulteriori miglioramenti delle prestazioni e aggiunge nuove funzionalità.

Ma quali sono esattamente i vantaggi? Diamo un'occhiata ad alcuni di essi:

  • .NET Core 3.0 ha introdotto immagini R2R che ridurranno i tempi di avvio delle applicazioni .NET Core.
  • Con la versione 2.2 è apparsa la Tier Compilation, grazie alla quale i programmatori passeranno meno tempo a lanciare un progetto.
  • Supporto per i nuovi standard .NET.
  • Supporto per una nuova versione del linguaggio di programmazione.
  • Ottimizzazione: con ogni nuova versione migliora l'ottimizzazione delle librerie di base Collection/Struct/Stream/String/Regex e molto altro ancora. Se stai eseguendo la migrazione da .NET Framework a .NET Core, otterrai subito un notevole incremento delle prestazioni. Ad esempio, allego un collegamento ad alcune delle ottimizzazioni aggiunte a .NET Core 3.0: https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-core-3-0/

Prestazioni in .NET Core

conclusione

Quando scrivi il codice, vale la pena prestare attenzione ai diversi aspetti del tuo progetto e utilizzare le funzionalità del tuo linguaggio di programmazione e della tua piattaforma per ottenere il miglior risultato. Sarei felice se condividessi le tue conoscenze relative all'ottimizzazione in .NET.

Collegamento a github

Fonte: habr.com

Aggiungi un commento