Prestazioni in .NET Core

Prestazioni in .NET Core

Prestazioni in .NET Core

Ciao a tutti! Questo articolo è una raccolta delle Best Practices che io e i miei colleghi utilizziamo da molto tempo lavorando su diversi progetti.

Informazioni sulla macchina su cui sono stati effettuati i calcoli:BenchmarkDotNet=v0.11.5, OS=Windows 10.0.18362
Intel Core i5-8250U CPU 1.60GHz (Kaby Lake R), 1 CPU, 8 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), RyuJIT 64bit
Core: .NET Core 2.2.7 (CoreCLR 4.6.28008.02, CoreFX 4.6.28008.03), RyuJIT 64bit
[Host]: .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), RyuJIT 64bit
Core: .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), RyuJIT 64bit

Job=Core Runtime=Core

ToList vs ToArray e Cicli


Avevo pianificato di preparare queste informazioni con il rilascio di .NET Core 3.0, ma mi hanno preceduto; non voglio rubare la gloria altrui e copiare informazioni altrui, quindi indicherò semplicemente il link a un buon articolo, dove viene descritta in dettaglio la comparazione.

Da parte mia, voglio solo presentarvi le mie misurazioni e i risultati; ho aggiunto in esse cicli inversi per gli amanti dello "stile C++" nella scrittura dei cicli.

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:

Prestazioni in .NET Core

Prestazioni in .NET Core

Possiamo concludere che l'elaborazione ciclica di una collezione di tipo Array è più rapida, grazie alle sue ottimizzazioni interne e alla chiara definizione delle dimensioni della collezione. È anche importante ricordare che una collezione di tipo List ha i suoi vantaggi e vale la pena utilizzare la collezione giusta a seconda delle operazioni necessarie. Anche se scrivi la logica di lavoro con i cicli, non dimenticare che si tratta di un normale loop e anche questo è soggetto a potenziali ottimizzazioni. È stato pubblicato su Habr un articolo qualche tempo fa: https://habr.com/ru/post/124910/. È ancora attuale e consigliato per la lettura.

Throw

Un anno fa lavoravo in un'azienda su un progetto legacy, in quel progetto era normale gestire la convalida dei campi tramite la costruzione try-catch-throw. Già allora capivo che questa era una logica di business malsana per il progetto, quindi cercavo di non utilizzare tale costruzione quando possibile. Ma vediamo quali sono i problemi nel gestire gli errori in questo modo. Ho scritto un codice semplice per confrontare due approcci e ho registrato delle “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 mostrano un risultato analogo (.NET Core 3.0):

Prestazioni in .NET Core

Prestazioni in .NET Core

Il try catch complica la comprensione del codice e aumenta il tempo di esecuzione del tuo programma. Ma se hai bisogno di questa struttura, evita di inserire quelle righe di codice per cui non ti aspetti la gestione degli errori: questo renderà il codice più comprensibile. In effetti, a sovraccaricare il sistema non è tanto la gestione delle eccezioni, quanto il lancio stesso degli errori attraverso la costruzione throw new Exception.

Lanciare eccezioni è più lento di qualche classe che raccoglierà l'errore nel formato necessario. Se gestisci un modulo o dei dati e sai chiaramente quale errore dovrebbe esserci, perché non gestirli?

Non vale la pena scrivere la costruzione throw new Exception() se questa situazione non è eccezionale. L'elaborazione e il lancio di un'eccezione costano molto!!!

ToLower, ToLowerInvariant, ToUpper, ToUpperInvariant

Nel mio quinquennale esperienza sulla piattaforma .NET ho incontrato molti progetti che utilizzavano il confronto delle stringhe. Ho anche visto la seguente situazione: c'era una soluzione Enterprise con molti progetti, ognuno dei quali effettuava il confronto delle stringhe in modo diverso. Ma cosa si deve usare e come unificare tutto ciò? Nel libro CLR via C# di Richter ho letto informazioni sul fatto che il metodo ToUpperInvariant() è più veloce di ToLowerInvariant().

Estratto dal libro:

Prestazioni in .NET Core

Ovviamente non ci ho creduto e ho deciso di effettuare alcuni test su .NET Framework e il risultato mi ha scioccato: un aumento delle prestazioni di oltre il 15%. Il giorno successivo al mio arrivo al lavoro ho mostrato i risultati delle misurazioni alla mia direzione e ho fornito loro accesso al codice sorgente. Dopo ciò, 2 progetti su 14 sono stati modificati secondo 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 le misurazioni per diverse versioni di .NET Core, affinché ognuno di voi possa fare una scelta verso la soluzione più ottimale. E voglio solo aggiungere che nella società 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();
        }

Prestazioni in .NET Core

Prestazioni in .NET Core

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

Prestazioni in .NET Core

Prestazioni in .NET Core

Compilazione a livelli

Nel mio precedente articolo ho descritto brevemente questa funzionalità e vorrei correggere e ampliare le mie parole. La compilazione di livelli multipli accelera il tempo di avvio della tua soluzione, ma si sacrifica il fatto che alcune parti del tuo codice verranno compilate in una versione più ottimizzata in background, il che può portare a piccole spese generali. Con l'avvento di .NET Core 3.0, il tempo di compilazione dei progetti con compilazione tier attivata è diminuito e sono stati risolti bug legati a questa tecnologia. In passato, questa tecnologia causava errori nelle prime richieste in ASP.NET Core e si bloccava durante la prima compilazione in modalità di compilazione multilivello. Al momento, in .NET Core 3.0 è abilitata per impostazione predefinita, ma puoi disattivarla se lo desideri. Se ricopri il ruolo di team lead, senior, middle o sei il responsabile di un dipartimento, devi capire che uno sviluppo rapido del progetto aumenta il valore del team e questa tecnologia ti permetterà di risparmiare tempo sia per gli sviluppatori che per il lavoro stesso del progetto.

.NET livello superiore

Aggiorna la tua versione di .NET Framework / .NET Core. Spesso, ogni nuova versione offre un ulteriore incremento delle prestazioni e aggiunge nuove funzionalità.

Ma quali sono i vantaggi specifici? Esaminiamo alcuni di essi:

  • In .NET Core 3.0 sono stati introdotti i profili R2R, che ridurranno il tempo di avvio delle applicazioni .NET Core.
  • Con la versione 2.2 è stata introdotta la Compilazione Tier, grazie alla quale i programmatori spenderanno meno tempo ad avviare i progetti.
  • Supporto per i nuovi standard .NET Standard.
  • Supporto per la 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. Se stai passando da .NET Framework a .NET Core, otterrai un grande incremento delle prestazioni out-of-the-box. Ad esempio, allego un link a parte delle ottimizzazioni che sono state aggiunte in .NET Core 3.0: https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-core-3-0/

Prestazioni in .NET Core

Conclusione

Quando scrivi codice, vale la pena prestare attenzione a diversi aspetti del tuo progetto e utilizzare le funzioni del tuo linguaggio di programmazione e della piattaforma per ottenere i migliori risultati. Sarò felice se condividerai le tue conoscenze relative all'ottimizzazione in .NET.

Link a github

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster