Stručný úvod
System.Collections.Generic.List <T>je všeobecná trieda kolekcií v .NET, ktorá dokáže ukladať akýkoľvek typ dát vďaka svojmu pohodliu a bohatému API, ktoré je široko používané v každodennom živote a možno ho označiť za najpoužívanejšiu triedu kolekcií.
Pri písaní kódu často potrebujeme prechádzať kolekciou zoznamov, <T>aby sme získali prvky v nej pre niektoré obchodné spracovania. Normálne nie je v množstve veľa prvkov a je veľmi rýchle prechádzanie. Ale pri spracovaní veľkých dát, štatistike, výpočtoch v reálnom čase a podobne<T>, ako rýchlo prechádzať zoznamom desiatok tisíc alebo stoviek tisíc dát? To je to, čo vám dnes potrebujem povedať.
Prechodový režim
Pozrime sa na výkon rôznych metód prechádzania a vytvorme nasledujúci výkonnostný benchmark, pričom použijeme rôzne rády triedy zberu na sledovanie výkonu rôznych metód. Úryvok kódu vyzerá takto:
Použite príkaz foreach
Foreach je najbežnejší spôsob, akým prechádzame kolekcie, ide o syntax sugar implementáciu iterátorového vzoru a používa sa aj ako referenčný bod pre toto obdobie.
Keďže príkaz foreach je syntaktický cukor, kompilátor nakoniec volá GetEnumerator() a MoveNext() pomocou while slučky na implementáciu funkcionality. Skompilovaný kód vyzerá takto:
Implementácia metódy MoveNext() zabezpečí, že v iterácii nebudú žiadne ďalšie vlákna upravovať kolekciu, a ak k úprave dôjde, vyhodí výnimku InvalidOperationException a vykoná overflow check, aby overila, či je aktuálny index legitímny, a tiež musí priradiť príslušný prvok enumerátorovi. Súčasný atribút,Takže v skutočnosti jeho výkon nie je najlepší, úryvok kódu vyzerá takto:
Pozrime sa, ako funguje naprieč rôznymi veľkosťami množín, a výsledky vyzerajú takto:
Je vidieť, že pri rôznych veľkostiach je potrebný lineárny rastový vzťah časovo náročného procesu, aj keď prechádza 100 W dát bez akejkoľvek logiky spracovania, trvá to aspoň 1 sekundu.
Použite metódu zoznamu ForEach
Ďalším bežným spôsobom je používať List<T>. Metóda ForEach(), ktorá vám umožní zadať <T>delegáta akcie, ktorý bude volať delegáta akcie počas jeho iterácie cez <T>prvok.
Je to <T>interná implementačná metóda Listu, takže môže priamo pristupovať k súkromným poliam a vyhnúť sa kontrole pretečenia. V teórii by to malo byť rýchle; Ale v našom prípade existuje len jedna prázdna metóda, ktorá sa nemusí správať dobre pri plne inline volaní foreach metódy. Nižšie je zdrojový kód metódy ForEevery (Zdroj), ktorý ukazuje, že nemá overflow checking, ale stále zachováva súčasné overovanie čísla verzie.
Okrem toho, keďže je potrebné odovzdať delegáta metóde ForEevery v volacom kóde, bude kontrolovať, či je objekt delegátu v triede generovania uzavretia zakaždým prázdny, a ak nie, nový Action<T>(), ako je uvedené nižšie:
Pozrime sa, ako sa porovnáva s kľúčovým slovom foreach z hľadiska výkonu. Nasledujúci obrázok ukazuje výsledky benchmarku:
Podľa výsledkov testu je to o 40 % pomalšie ako priame použitie kľúčového slova foreach, zdá sa, že ak to nie je nevyhnutné, je lepšie použiť foreach priamo, takže existuje nejaký rýchlejší spôsob?
pre prechod slučkou
Vracajúc sa k nášmu najstaršiemu spôsobu, ktorým je používať kľúčové slovo for na prechádzanie kolekciou. Momentálne by to mala byť najvýkonnejšia metóda prechádzania, pretože nevyžaduje nejaký redundantný kód ako predchádzajúce (hoci indexer je tiež kontrolovaný, aby sa zabránilo pretečeniu), a samozrejme nekontroluje číslo verzie, takže v multithreaded prostredí sa kolekcia mení a pri použití pre nebude vyhodená žiadna výnimka. Testovací kód vyzerá takto:
Uvidíme, ako to dopadne.
Zdá sa, že takto to očakávame.Použitie priamo for-loopu je o 60 % rýchlejšie ako foreachsada, ktorá kedysi trvala 1 sekundu na prechod, teraz trvá len 400 milisekúnd. Existuje teda rýchlejší spôsob?
Využitie zbierokMaršal
Po .NET5 komunita dotnet implementovala triedu CollectionsMarshal, aby zlepšila výkon operácií zbierky. Táto trieda implementuje, ako pristupovať k natívnym poliam typov kolekcií (ak ste videli môj [. .NET Performance Optimization – mali by ste nastaviť počiatočnú veľkosť pre typy kolekcií, viete, že základnou implementáciou mnohých dátových štruktúr sú polia). Takže dokáže preskočiť všetky druhy detekcií a priamo pristupovať k pôvodnému poľu, ktoré by malo byť najrýchlejšie. Kód vyzerá takto:
Vidíte, že kód generovaný kompilátorom je veľmi efektívny.
Priamy prístup k podkladovému poľu je veľmi nebezpečný, musíte vedieť, čo robíte s každým riadkom kódu, a mať dostatočné testovanie. Výsledky benchmarku sú nasledovné:
WowPoužívanie CollectionsMarshal je o 79 % rýchlejšie ako použitie foreach, ale malo by to byť dôvodom optimalizácie JIT, nie je veľký rozdiel medzi používaním foreach a pre kľúčové slovo loop Span.
súhrn
Dnes som s vami hovoril o tom, ako rýchlo prechádzať kolekciou List, a vo väčšine prípadov sa odporúča použiť kľúčové slovo foreach, ktoré má overflow checking aj viacvláknovú kontrolu verziových čísel, čo nám môže uľahčiť písanie správneho kódu.
Ak potrebujete vysoký výkon a veľké objemy dát, odporúča sa použiť pre a CollectionsMarshal.AsSpan priamo na prechádzanie kolekciou.
Odkaz na zdrojový kód tohto článku:
Prihlásenie na hypertextový odkaz je viditeľné.
Pôvodný odkaz:Prihlásenie na hypertextový odkaz je viditeľné.
|