Krátký úvod
Optimalizace výkonu je způsob, jak zajistit, aby stejný počet požadavků byl zpracován s menším počtem zdrojů, což jsou obvykle CPU nebo paměť, a samozřejmě zpracování operačního systému IO, síťový provoz, využití disku atd. Ale většinou snižujeme využití CPU a paměti. Obsah sdílený dříve má určitá omezení, je obtížné ho přímo transformovat, dnes vám chci představit jednoduchou metodu, stačí nahradit několik typů kolekcí, aby se dosáhlo zlepšení výkonu a snížení paměťové náročnosti. Dnes vám chci představit třídní knihovnu, totoKnihovna tříd se jmenuje Collections.Pooled, Jak je vidět z názvu, je to prostřednictvím sdílené paměti, aby se dosáhlo cíle snížení paměťové stopy a GC, a my přímo uvidíme, jaký je jeho výkon, a také vás zavedeme do zdrojového kódu, proč přináší tato zlepšení výkonu.
Sbírky. Spojené
Odkaz na projekt:Přihlášení k hypertextovému odkazu je viditelné.
Knihovna je založena na třídách v System.Collections.Generic, které byly upraveny tak, aby využívaly nové knihovny tříd System.Span <T>a System.Buffers.ArrayPool <T>za účelem snížení alokace pamětí, zlepšení výkonu a umožnění větší interoperability s moderními API. Collections.Pooled podporuje .NET Standnd 2.0 (.NET Framework 4.6.1+) a také podporu pro . NET Core 2.1+. Rozsáhlá sada jednotkových testů a benchmarků byla portována z CoreFX.
Celkový počet testů: 27 501. Vir: 27501. Neúspěch: 0. Skip: 0. Zkušební provoz byl úspěšný. Doba provedení testu: 9,9019 sekundy
Jak používat
Tuto knihovnu můžete snadno nainstalovat přes Nuget, NuGet verzi.
V knihovně Collections.Pooled implementuje poolované verze pro typy kolekcí, které běžně používáme, jak je vidět ve srovnání s nativními typy .NET.
| . .NET native | Sbírky. Spojené | poznámka | | Seznam<T> | PooledList<T> | Generické kolekční třídy | | Slovník<TKey, TValue> | PooledDictionary<TKey, TValue> | Třída obecného slovníku | | HashSet<T> | PooledSet<T> | Generické třídy sběru hashů | | Zásobník<T> | Zásobník<T> | Generické zásobníky | | Fronta<T> | PooledQueue<T> | Generická kohorta |
Při použití stačí přidat odpovídající . .NET nativní verze s verzí Collections.Pooled, jak je uvedeno v kódu níže:
Musíme však poznamenat, že typ Pooled implementuje rozhraní IDispose, které vrací použitou paměť do poolu pomocí metody Dispose(), takže je potřeba zavolat jeho metodu Dispose() po použití objektu Pooled collection. Nebo můžete použít klíčové slovo using var přímo.
Poznámka: Použijte objekt kolekce uvnitř Collections.PooledNejlepší je uvolnit ručně, ale nezáleží na tom, jestli ho neuvolníte, Zelená karta ho nakonec recykluje, ale nemůže být vrácen do poolu a nezpůsobí to úsporu paměti. Protože znovu využívá paměťový prostor, při vracení paměťového prostoru do poolu musí zpracovat prvky v kolekci a poskytuje enumeraci nazvanou ClearMode, definovanou následovně:
Ve výchozím nastavení můžete použít výchozí hodnotu Auto a pokud jsou speciální požadavky na výkon, můžete použít Never po znalosti rizik. U typů referencí a typů hodnot obsahujících typy referencí musíme při vracení paměťového prostoru do poolu vyprázdnit referenci pole, pokud není vymazáno, GC nebude schopen tuto část paměťového prostoru uvolnit (protože reference prvku byla vždy držena v poolu), pokud je to čistý typ hodnoty, nelze ji vyprázdnit, v tomto článku popisuji rozdíl v paměti mezi typy referencí a polem struct (typ hodnoty), čisté typy hodnot nemají recyklaci objektových hlaviček a nevyžadují zásah GC.
. .NET Performance Optimization – Použijte alternativní třídy struktur:Přihlášení k hypertextovému odkazu je viditelné.
Srovnání výkonu
Benchmark jsem nedělal sám a běžné skóre open source projektů, které jsem přímo použil, bylo 0 pro využití paměti mnoha projektů, což bylo proto, že poolovaná paměť neměla žádnou další alokaci.
PooledList<T>
Projděte si prvky z roku 2048 přidané do sady v Benchmarku. .NET native List <T>vyžaduje 110 USD (podle skutečných výsledků benchmarku by měly být milisekundy v obrázku administrativní chybou) a 263KB paměti, zatímco PooledList <T>potřebuje pouze 36 USD a 0KB paměti.
PooledDictionary<TKey, TValue>
Přidejte 10_0000 prvků do slovníku v smyčce v Benchmarku, . .NET nativní slovník<TKey, TValue> vyžaduje 11ms a 13MB paměti, zatímco PooledDictionary<TKey, TValue> vyžaduje pouze 7ms a 0MB paměti.
PooledSet<T>
Projděte smyčkou hash kolekce v Benchmarku, přidejte 10_0000 prvků, . .NET nativní HashSet <T>vyžaduje 5348ms a 2MB, zatímco PooledSet <T>vyžaduje pouze 4723ms a 0MB paměti.
PooledStack<T>
Projděte zásobník v Benchmarku a přidejte 10_0000 prvků. .NET nativní PooledStack <T>vyžaduje 1079ms a 2MB, zatímco PooledStack <T>vyžaduje pouze 633ms a 0MB paměti.
PooledQueue<T>
Projděte smyčky v Benchmarku a přidejte do fronty 10_0000 prvků, . .NET <T>nativní PooledQueue vyžaduje 681ms a 1MB, zatímco PooledQueue <T>vyžaduje pouze 408ms a 0MB paměti.
Scéna se ručně neuvolňuje
Navíc jsme zmínili výše, že je třeba uvolnit typ sbírky ve skupině, ale nezáleží na tom, jestli není uvolněn, protože GC bude recyklovat.
Výsledky benchmarku jsou následující:
Závěr lze vyvodit z výše uvedených výsledků benchmarku.
Uvolnění kolekce Pooled typů včas sotva spustí GC a přidělí paměť, podle výše uvedeného grafu přiděluje pouze 56 bajtů paměť. I když kolekce typů Pooled není uvolněna, protože alokuje paměť z poolu, stále bude znovu používat paměť během operace rozšíření ReSize a přeskočí krok inicializace GC allocation paměti, který je relativně rychlý. Nejpomalejší je použít normální typ sbírky, každá operace rozšíření ReSize musí aplikovat na nový paměťový prostor a GC také musí získat zpět předchozí paměťový prostor.
Hlavní analýza
Pokud jste četli můj předchozí blogový příspěvek, měli byste nastavit počáteční velikost typů kolekcí a analyzovat princip implementace C# Dictionary, můžete vědět, že vývojáři .NET BCL používají základní datové struktury těchto základních typů kolekcí jako pole pro vysoce výkonný náhodný přístup, vezměme <T>si například List.
Vytvořte nové pole pro uložení přidaných prvků. Pokud v poli není dostatek místa, operace rozšíření se spustí a požádá o dvojnásobek velikosti prostoru. Kód konstruktoru je následující a můžete vidět, že jde o generické pole vytvořené přímo:
Takže pokud chcete poolovat paměť, stačí změnit místo, kde se nová aplikace pro klíčové slovo použije v knihovně tříd, aby mohla použít pooled application. Zde se o to s vámi podělím. NET BCL je typ nazývaný ArrayPool, který poskytuje zdrojový fond pole s opakovaně použitelnými generickými instancemi, jež lze použít ke snížení tlaku na GC a zlepšení výkonu při častém vytváření a ničení pole.
Základní vrstvou našeho typu Pooled je použití ArrayPool ke sdílení zdrojových poolů a z jeho konstruktoru vidíme, že ArrayPool používá ve výchozím nastavení<T>. Sdílené pro přiřazení objektů pole a samozřejmě si můžete vytvořit vlastní ArrayPool, abyste ho mohli používat.
Navíc při provádění operace úpravy kapacity (expanze) je staré pole vráceno do poolu vláken a nové pole je také získáno z poolu.
Kromě toho autor používá Span k optimalizaci API jako Add a Insert, aby jim zajistil lepší výkon při náhodném přístupu. Navíc bylo přidáno API série TryXXX, takže ho můžete používat pohodlněji. Například <T>třída List <T>má až 170 modifikací oproti PooledList.
shrnutí
Při našem skutečném online použití můžeme nahradit nativní typ kolekce typem kolekce poskytovaným Pooled, což je velmi užitečné pro snížení spotřeby paměti a latence P95. A i když na to zapomenete, výkon nebude o moc horší než při použití nativního typu kolekce. Samozřejmě, nejlepší zvyk je včas ho pustit.
Původní:Přihlášení k hypertextovému odkazu je viditelné.
|