Krátký úvod
System.Collections.Generic.List <T>je generická třída kolekcí v .NET, která může ukládat jakýkoli typ dat díky své pohodlnosti a bohatému API, jež je široce používána v našem každodenním životě a lze ji označit za nejpoužívanější třídu kolekcí.
Při psaní kódu často musíme procházet sbírku Seznamů, <T>abychom získali prvky v ní pro některé obchodní zpracování. Obvykle v sadě není mnoho prvků a je velmi rychlé se projít. Ale jak rychle procházet sběr dat v databázi, statistiku, výpočetní techniku v reálném čase atd<T>., jak rychle procházet seznam desítek tisíc nebo stovek tisíc dat? To je to, co vám dnes musím sdělit.
Režim pohybu
Podívejme se na výkon různých metod procházení a sestavme následující benchmark výkonu, přičemž využijeme různé řády velikosti při procházení sběru, abychom zjistili výkon různých metod. Úryvek kódu vypadá takto:
Použijte příkaz foreach
Foreach je nejběžnější způsob, jak procházíme sbírky, jedná se o syntaxi cukrovou implementaci vzoru iterátoru a zároveň slouží jako referenční bod pro tuto dobu.
Protože příkaz foreach je syntaktický cukr, kompilátor nakonec volá GetEnumerator() a MoveNext() pomocí while-loopu pro implementaci funkce. Zkompilovaný kód vypadá takto:
Implementace metody MoveNext() zajistí, že v iteraci nebudou v iteraci žádná další vlákna upravovat kolekci, a pokud k úpravě dojde, vyhodí výjimku InvalidOperationException a provede overflow check, aby ověřila, zda je aktuální index legitimní, a také musí přiřadit odpovídající prvek enumerátoru. Současná vlastnost,Takže ve skutečnosti není jeho výkon nejlepší, úryvek kódu vypadá takto:
Podívejme se, jak si vede napříč různými velikostmi sad, a výsledky vypadají takto:
Je vidět, že v případě různých velikostí je lineární růstový vztah časově náročného procesu vyžadován, i když prochází 100 W dat bez jakékoli logiky zpracování, trvá to alespoň 1 sekundy.
Použijte metodu ForEach pro seznam
Dalším běžným způsobem je použití <T>Seznamu. Metoda ForEach(), která umožňuje předat <T>delegáta Akce, který bude volat delegáta Akce při iteraci prvkem<T>.
Jedná se o <T>interní implementační metodu Listu, takže může přímo přistupovat k privátním polím a vyhnout se kontrole přetečení. Teoreticky by to mělo být rychlé; Ale v našem případě je jen jedna prázdná metoda, která nemusí fungovat dobře při plně inline volání foreach metody. Níže je zdrojový kód metody ForEevery (ForEach), který ukazuje, že nemá kontrolu přetečení, ale stále zachovává současnou kontrolu čísla verze.
Navíc, protože je nutné předat delegáta metodě ForEevery v call kódu, zkontroluje, zda je objekt delegátu v třídě generování uzavření pokaždé prázdný, a pokud ne, tak nový Action<T>(), jak je uvedeno níže:
Podívejme se, jak se srovnává s klíčovým slovem foreach z hlediska výkonu. Následující obrázek ukazuje výsledky benchmarku:
Podle výsledků testu je to o 40 % pomalejší než přímé použití klíčového slova foreach, zdá se, že pokud to není nutné, je lepší použít foreach přímo, takže existuje nějaký rychlejší způsob?
pro průchod smyčky
Vrátíme se k našemu nejstaršímu způsobu, kterým je používat klíčové slovo for pro procházení sbírkou. Měla by být momentálně nejlépe fungující metoda procházení, protože nevyžaduje nějaký redundantní kód jako předchozí (i když indexer je také kontrolován, aby zabránil přetečení), a samozřejmě nekontroluje číslo verze, takže v multithreaded prostředí se kolekce mění a při použití pro není žádná výjimka. Testovací kód vypadá takto:
Uvidíme, jak to dopadne.
Zdá se, že takto to očekáváme.Použití smyčky for přímo je o 60 % rychlejší než foreach, sada, která dříve trvala 1 sekundu na přejezd, nyní trvá jen 400 milisekund. Existuje tedy rychlejší způsob?
Využití kolekcíMaršále
Po .NET5 komunita dotnet implementovala třídu CollectionsMarshal, aby zlepšila výkon inkasačních operací. Tato třída implementuje, jak přistupovat k nativním polím typů kolekcí (pokud jste viděli můj [. .NET Performance Optimization – měli byste nastavit počáteční velikost pro typy kolekcí, víte, že základní implementací mnoha datových struktur jsou pole). Takže může přeskočit všechny typy detekcí a přímo přistupovat k původnímu poli, které by mělo být nejrychlejší. Kód vypadá takto:
Vidíte, že kód generovaný kompilátorem je velmi efektivní.
Přímý přístup k základnímu poli je velmi nebezpečný, musíte vědět, co děláte s každým řádkem kódu, a mít dostatek testování. Výsledky benchmarku jsou následující:
PániPoužívání CollectionsMarshal je o 79 % rychlejší než použití foreach, ale to by mělo být důvodem optimalizace JIT, není velký rozdíl mezi použitím foreach a pro klíčové slovo loop Span.
shrnutí
Dnes jsem s vámi mluvil o tom, jak rychle procházet sbírku Seznam, a ve většině případů se doporučuje použít klíčové slovo foreach, které má jak overflow checking, tak vícevláknovou kontrolu verzí, což nám může usnadnit psaní správného kódu.
Pokud potřebujete vysoký výkon a velké objemy dat, doporučuje se použít přímo pro a CollectionsMarshal.AsSpan pro procházení sbírky.
Odkaz na zdrojový kód tohoto článku:
Přihlášení k hypertextovému odkazu je viditelné.
Původní odkaz:Přihlášení k hypertextovému odkazu je viditelné.
|