Prestanda i .NET Core

Prestanda i .NET Core

Prestanda i .NET Core

Hej alla! Den här artikeln är en samling av bästa praxis som jag och mina kollegor har använt under lång tid när vi har arbetat med olika projekt.

Information om maskinen som beräkningarna utfördes på:BenchmarkDotNet=v0.11.5, OS=Windows 10.0.18362
Intel Core i5-8250U CPU 1.60 GHz (Kaby Lake R), 1 CPU, 8 logiska och 4 fysiska kärnor
.NET Core SDK=3.0.100
[Värd]: .NET Core 2.2.7 (CoreCLR 4.6.28008.02, CoreFX 4.6.28008.03), 64bit RyuJIT
Kärna: .NET Core 2.2.7 (CoreCLR 4.6.28008.02, CoreFX 4.6.28008.03), 64bit RyuJIT
[Värd]: .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), 64bit RyuJIT
Kärna: .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), 64bit RyuJIT

Job=Core Runtime=Core

ToList vs ToArray och cykler


Jag planerade att förbereda denna information med lanseringen av .NET Core 3.0, men de slog mig till det, jag vill inte stjäla någon annans ära och kopiera andras information, så jag ska bara påpeka länk till en bra artikel där jämförelsen beskrivs i detalj.

För min egen räkning vill jag bara presentera mina mätningar och resultat för dig; jag lade till omvända loopar till dem för älskare av "C++-stilen" för att skriva loopar.

Koda:

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

Prestandahastigheterna i .NET Core 2.2 och 3.0 är nästan identiska. Här är vad jag kunde få i .NET Core 3.0:

Prestanda i .NET Core

Prestanda i .NET Core

Vi kan dra slutsatsen att iterativ bearbetning av en Array-samling är snabbare på grund av dess interna optimeringar och explicita samlingsstorleksallokering. Det är också värt att komma ihåg att en Listsamling har sina egna fördelar och du bör använda rätt samling beroende på vilka beräkningar som krävs. Även om du skriver logik för att arbeta med loopar, glöm inte att detta är en vanlig loop och den är även föremål för eventuell loopoptimering. En artikel publicerades på habr för ganska länge sedan: https://habr.com/ru/post/124910/. Den är fortfarande relevant och rekommenderad läsning.

Kasta

För ett år sedan arbetade jag på ett företag med ett äldre projekt, i det projektet var det normalt att bearbeta fältvalidering genom en try-catch-throw-konstruktion. Jag förstod redan då att detta var ohälsosam affärslogik för projektet, så när det var möjligt försökte jag att inte använda en sådan design. Men låt oss ta reda på varför tillvägagångssättet för att hantera fel med en sådan konstruktion är dåligt. Jag skrev en liten kod för att jämföra de två metoderna och gjorde riktmärken för varje alternativ.

Koda:

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

Resultaten i .NET Core 3.0 och Core 2.2 har ett liknande resultat (.NET Core 3.0):

Prestanda i .NET Core

Prestanda i .NET Core

Try catch gör koden svårare att förstå och ökar körtiden för ditt program. Men om du behöver den här konstruktionen bör du inte infoga de kodraderna som inte förväntas hantera fel – det kommer att göra koden lättare att förstå. I själva verket är det inte så mycket hanteringen av undantag som laddar systemet, utan snarare slängningen av fel själva genom den nya undantagskonstruktionen.

Att kasta undantag är långsammare än någon klass som samlar in felet i det format som krävs. Om du bearbetar ett formulär eller vissa uppgifter och du tydligt vet vad felet ska vara, varför inte bearbeta det?

Du bör inte skriva en throw new Exception()-konstruktion om denna situation inte är exceptionell. Att hantera och kasta ett undantag är väldigt dyrt!!!

ToLower, ToLowerInvariant, ToUpper, ToUpperInvariant

Under mina 5 års erfarenhet av att arbeta på .NET-plattformen har jag stött på många projekt som använde strängmatchning. Jag såg också följande bild: det fanns en Enterprise-lösning med många projekt, som var och en utförde strängjämförelser på olika sätt. Men vad ska användas och hur man förenar det? I boken CLR via C# av Richter läste jag information om att metoden ToUpperInvariant() är snabbare än ToLowerInvariant().

Utdrag ur boken:

Prestanda i .NET Core

Naturligtvis trodde jag inte på det och bestämde mig för att köra några tester då på .NET Framework och resultatet chockade mig - mer än 15% prestandaökning. Sedan, när jag kom till jobbet nästa morgon, visade jag dessa mätningar för mina överordnade och gav dem tillgång till källkoden. Efter detta ändrades 2 av 14 projekt för att passa de nya mätningarna, och med tanke på att dessa två projekt fanns för att bearbeta enorma Excel-tabeller var resultatet mer än betydande för produkten.

Jag presenterar också för dig mått för olika versioner av .NET Core, så att var och en av er kan göra ett val mot den mest optimala lösningen. Och jag vill bara tillägga att i företaget där jag arbetar använder vi ToUpper() för att jämföra strängar.

Koda:

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

Prestanda i .NET Core

Prestanda i .NET Core

I .NET Core 3.0 är ökningen för var och en av dessa metoder ~x2 och balanserar implementeringarna sinsemellan.

Prestanda i .NET Core

Prestanda i .NET Core

Nivåkompilering

I min senaste artikel beskrev jag denna funktion kortfattat, jag skulle vilja korrigera och komplettera mina ord. Kompilering på flera nivåer påskyndar uppstartstiden för din lösning, men du offrar att delar av din kod kommer att kompileras till en mer optimerad version i bakgrunden, vilket kan introducera en liten overhead. Med tillkomsten av NET Core 3.0 har byggtiden för projekt med tierkompilering aktiverad minskat och buggar associerade med denna teknik har åtgärdats. Tidigare ledde denna teknik till fel i de första förfrågningarna i ASP.NET Core och fryser under den första byggnaden i kompileringsläge på flera nivåer. Det är för närvarande aktiverat som standard i .NET Core 3.0, men du kan inaktivera det om du vill. Om du är i positionen som team-lead, senior, middle, eller om du är chef för en avdelning, då måste du förstå att snabb projektutveckling ökar värdet av teamet och denna teknik kommer att göra det möjligt för dig att spara tid för båda utvecklarna och tidpunkten för själva projektet.

.NET nivå upp

Uppgradera din .NET Framework/.NET Core-version. Ofta ger varje ny version ytterligare prestandavinster och lägger till nya funktioner.

Men exakt vilka är fördelarna? Låt oss titta på några av dem:

  • .NET Core 3.0 introducerade R2R-bilder som kommer att minska starttiden för .NET Core-applikationer.
  • Med version 2.2 dök Tier Compilation upp, tack vare vilken programmerare kommer att spendera mindre tid på att starta ett projekt.
  • Stöd för nya .NET-standarder.
  • Stöd för en ny version av programmeringsspråket.
  • Optimering, med varje ny version förbättras optimeringen av basbiblioteken Collection/Struct/Stream/String/Regex och mycket mer. Om du migrerar från .NET Framework till .NET Core får du en stor prestandaökning direkt. Som ett exempel bifogar jag en länk till några av de optimeringar som lades till i .NET Core 3.0: https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-core-3-0/

Prestanda i .NET Core

Slutsats

När du skriver kod är det värt att uppmärksamma olika aspekter av ditt projekt och använda funktionerna i ditt programmeringsspråk och plattform för att uppnå bästa resultat. Jag skulle bli glad om du delar med dig av din kunskap om optimering i .NET.

Länk till github

Källa: will.com

Lägg en kommentar