Kort introduktion
System.Collections.Generic.List <T>är en generisk samlingsklass i .NET, som kan lagra vilken typ av data som helst tack vare sin bekvämlighet och rika API, som används flitigt i våra dagliga liv och kan sägas vara den mest använda samlingsklassen.
Vid kodskrivning behöver vi ofta iterera genom en <T>List-samling för att få fram elementen i den för viss affärsbearbetning. Normalt finns det inte många element i en uppsättning och den är väldigt snabb att ta sig igenom. Men för viss big data-bearbetning, statistik, realtidsberäkning osv.<T>, hur kan man snabbt ta sig igenom listsamlingen av tiotusentals eller hundratusentals data? Det är det jag behöver dela med dig idag.
Traverseringsläge
Låt oss titta på prestandan för olika traverseringsmetoder och bygga följande prestandabenchmark, med olika storleksordningar för samlingstraversering för att se prestandan för olika metoder. Kodsnipet ser ut så här:
Använd foreach-satsen
Foreach är det vanligaste sättet vi går igenom samlingar på, det är en syntax sugar-implementation av iteratormönstret, och används också som en referenspunkt för denna tid.
Eftersom foreach-satsen är en syntaxsocker, anropar kompilatorn så småningom GetEnumerator() och MoveNext() med en while-loop för att implementera funktionen. Den kompilerade koden ser ut så här:
Implementeringen av MoveNext()-metoden säkerställer att inga andra trådar ändrar samlingen i iterationen, och om modifieringen sker kommer den att kasta ett InvalidOperationException-undantag, och den kommer att göra en överflödeskontroll för att kontrollera om det aktuella indexet är legitimt, och den måste också tilldela motsvarande element till enumeratorn. Aktuell attribut,Så i själva verket är dess prestanda inte den bästa, kodsnutten ser ut så här:
Låt oss titta på hur den presterar över olika setstorlekar, och resultaten ser ut så här:
Det kan ses att i fallet med olika storlekar krävs den linjära tillväxtrelationen för den tidskrävande processen, även om den passerar 100 W data utan någon bearbetningslogik, tar det minst 1 sekund.
Använd ForEach-metoden List
Ett annat vanligt sätt är att använda List<T>. ForEach()-metoden, som låter dig skicka in en <T>Action-delegat, som anropar Action-delegaten medan den itererar genom elementet<T>.
Det är en <T>intern implementeringsmetod för List, så den kan direkt komma åt privata arrayer och undvika överflödeskontroller. I teorin borde det gå snabbt; Men i vårt scenario finns det bara en tom metod, som kanske inte fungerar bra med ett fullt inline-anrop till foreach-metoden. Nedan finns källkoden till ForEach-metoden, som visar att den inte har överflödeskontroll, men att den ändå behåller samtidigt versionsnummerkontroll.
Dessutom, eftersom det är nödvändigt att skicka en delegat till ForEach-metoden, kommer den i anropskoden att kontrollera om delegatobjektet i slutningsgenereringsklassen är tomt varje gång, och om inte, ny Action<T>(), som visas nedan:
Låt oss titta på hur det jämförs med foreach-nyckelordet när det gäller prestanda. Följande bild visar resultaten från benchmarken:
Baserat på testresultaten är det 40 % långsammare än att använda foreach-nyckelordet direkt, det verkar som att om det inte är nödvändigt är det ett bättre val att använda foreach direkt, så finns det något snabbare sätt?
för looptraversering
Tillbaka till vårt äldsta sätt, som är att använda for-nyckelordet för att navigera i samlingen. Det borde vara den bäst presterande traverseringsmetoden just nu, eftersom den inte kräver redundant kod som de tidigare (även om indexeraren också är kontrollerad för att förhindra överflöden), och uppenbarligen kontrollerar den inte versionsnumret, så i en multitrådad miljö ändras samlingen och det kommer inte att kastas något undantag när man använder for. Testkoden ser ut så här:
Vi får se hur det blir.
Det verkar vara så vi förväntar oss.Att använda for-loopen direkt är 60 % snabbare än foreach, en uppsättning som tidigare tog 1 sekund att ta sig fram, nu bara tar 400 millisekunder. Finns det ett snabbare sätt?
Använd CollectionsMarshal
Efter .NET5 implementerade dotnet-communityn klassen CollectionsMarshal för att förbättra prestandan i samlingsoperationerna. Denna klass implementerar hur man kommer åt inbyggda arrayer av samlingstyper (om du har sett min [. .NET Performance Optimization - Du bör ställa in initialstorleken för samlingstyper], artikeln vet du att den underliggande implementeringen av många datastrukturer är arrays). Så den kan hoppa över alla slags upptäckter och direkt komma åt originalarrayen, vilket borde vara snabbast. Koden ser ut så här:
Du kan se att koden som genereras av kompilatorn är mycket effektiv.
Direkt åtkomst till den underliggande matrisen är mycket farligt, du måste veta vad du gör med varje kodrad och ha tillräcklig testning. Benchmarkresultaten är följande:
OjAtt använda CollectionsMarshal är 79 % snabbare än att använda foreach, men det borde vara anledningen till JIT-optimering, det finns ingen stor skillnad mellan att använda foreach och för nyckelordsloop Span.
sammanfattning
Idag pratade jag med dig om hur man snabbt går igenom List-samlingen, och i de flesta fall rekommenderas det att använda foreach-nyckelordet, som har både överflödeskontroll och multitrådad versionsnummerkontroll, vilket kan göra det enklare för oss att skriva rätt kod.
Om du behöver högpresterande och stora datavolymer rekommenderas det att använda CollectionsMarshal.AsSpan direkt för att navigera i samlingen.
Källkodslänk till denna artikel:
Inloggningen med hyperlänken är synlig.
Originallänk:Inloggningen med hyperlänken är synlig.
|