Měření výkonu kódu a stanovení referenčních hodnot

Dokončeno

Měření výkonu kódu a stanovení směrných plánů je důležitým krokem v procesu optimalizace. Poskytuje referenční bod pro vyhodnocení výsledků všech provedených změn.

Vytvoření standardních hodnot výkonu

Směrný plán výkonu je sada měření, která zachycuje, jak kód funguje před provedením jakýchkoli změn. Je to váš referenční bod. Bez směrného plánu pro porovnání nevíte, jestli vaše aktualizace zlepšují výkon kódu nebo zhoršovaly věci.

Tady je několik klíčových aspektů k měření na základní úrovni:

  • Doba provádění (latence): Jak dlouho trvá provedení určité operace nebo procesu? Tato hodnota se dá měřit u malé funkce (například trvá 5 milisekund seřadit 10 000 položek) nebo u kompletního scénáře (například služba reaguje na požadavek za 250 milisekund).

  • Propustnost: Kolik operací je možné v daném čase provést? Tato hodnota je relevantní pro serverové aplikace (požadavky za sekundu), dávkové zpracování (záznamy zpracovávané za minutu) atd.

  • Využití prostředků: Využití paměti (pracovní sada, přidělení, frekvence uvolňování paměti), využití procesoru (zatěžuje jádro na 100%? na jak dlouho?), diskové I/O, síťové I/O atd., v závislosti na tom, co kód dělá.

  • Charakteristiky škálovatelnosti: Jak se výše uvedené metriky mění při růstu velikosti vstupu nebo při nárůstu zatížení (souběžní uživatelé)? Můžete změřit, že funkce pro 100 položek trvá 0,1 milisekund a 10 milisekund pro 10 000 položek. To znamená, jak se škáluje (v tomto případě přibližně lineární s počtem položek). Tento druh směrného plánu vám pomůže pochopit škálovatelnost a nastavit cíle. Například můžete chtít 100 tisíc položek řadit do 200 milisekund, což může znamenat, že potřebujete efektivnější algoritmus nebo paralelní řazení.

Při stanovení základní úrovně:

  • Udělejte prostředí co nejkonzistentnější. Spusťte testy na stejném počítači za podobných podmínek (zavřete programy na pozadí, použijte sestavení vydané verze atd.). Výkon se může lišit při každém spuštění; minimalizace šumu poskytuje spolehlivější data.

  • Používejte buildy vydaných verzí a realistická nastavení. Ladění sestavení často běží pomaleji kvůli dodatečným kontrolám a nedostatku optimalizací. Pokud měříte rychlost algoritmu, zkompilujte ji v režimu vydání s optimalizacemi. Podobně pokud je váš kód vícevláknový nebo asynchronní, otestujte ho ve scénáři, který se podobá produkčnímu prostředí (například stejný počet vláken nebo stejná velikost datové sady).

  • Automatizujte nebo skriptujte měření. Kód byste měli spustit několikrát. Pomáhá mít jednoduchý srovnávací nástroj nebo používat nástroje, které ho automatizují. Například u malé funkce můžete napsat smyčku, která ji volá, měří čas a vypočítá průměr.

  • Zaznamenejte čísla. Vedení záznamu o základních metrikách (čas, paměť atd.). Pokud máte test jednotek nebo výstup protokolu, který vytiskne informace o výkonu, uložte je. V případě netriviálních případů je použití tabulky nebo jednoduché tabulky k porovnání "před a po" užitečné.

Základní měření pomocí stopek

Jazyk C# nabízí System.Diagnostics.Stopwatch třídu, která je užitečná pro měření doby provádění bloků kódu.

Předpokládejme například, že máte následující ProcessData() metodu, kterou máte podezření, že je pomalá:

var sw = Stopwatch.StartNew();
ProcessData();
sw.Stop();
Console.WriteLine($"ProcessData() completed in {sw.ElapsedMilliseconds} milliseconds");

Představte si, že tento kód konzistentně vytiskne hodnotu přibližně 120 milisekund. Tato průměrná hodnota je vaše základní hodnota pro ProcessData() za použitých testovacích podmínek.

Směrný plán pro využití paměti

Metoda GC.GetTotalMemory je volání metody .NET, které načte aktuální množství paměti přidělené garbage kolektorem (GC). Tato GetTotalMemory metoda zachycuje aktuální stav přidělení paměti při vyvolání.

Využití paměti můžete zachytit kontrolou GC.GetTotalMemory(false) před a po operaci.

Předpokládejme například, že máte následující kód:

long memoryBefore = GC.GetTotalMemory(forceFullCollection: true);
ProcessData();
long memoryAfter = GC.GetTotalMemory(forceFullCollection: true);
Console.WriteLine($"Memory used by ProcessData(): {memoryAfter - memoryBefore} bytes");

Pokud tento kód vytiskne hodnotu "Memory used by ProcessData(): 50 000 bytes", znamená to, kolik paměti bylo operaci přiděleno (čistý výpočet). Tato hodnota může být součástí vaší základní linie, zejména pokud máte obavy o režii na paměť nebo uvolňování paměti.

Kontextové základny

Základní hodnota často není jediné číslo. Můžete mít základní hodnoty v určitém měřítku a pak testovat ve větším měřítku. Například:

  • Řazení 10 000 položek trvá 50 milisekund (základní úroveň při 10 000).
  • Řazení 100 tisíc položek trvá 800 milisekund.

Tento výsledek značí, že výkon se snižuje při růstu vstupu, což se očekává. Také naznačuje, že algoritmus může být horší než O(n log n), protože zvýšení položek faktorem 10 se zdá zvýšit čas o faktor 16. Tento typ směrného plánu vám pomůže pochopit škálovatelnost a nastavit cíle.

Nástroje a techniky měření výkonu

Pochopení výkonu vyžaduje měření i analýzu algoritmických charakteristik. Než se ponoříte do nástrojů pro měření, je důležité porozumět klíčovým konceptům výkonu:

Principy algoritmické složitosti

Časová složitost popisuje, jak se běhový čas algoritmu zvětšuje při nárůstu velikosti vstupu, obvykle vyjádřená pomocí notace Big O:

  • O(1) – Konstantní čas: Výkon se nezmění se vstupní velikostí (například vyhledávání slovníku).
  • O(n) – Lineární čas: Výkon roste úměrně se vstupem (například iterace prostřednictvím seznamu jednou).
  • O(n²) – Kvadratický čas: Výkon roste s čtvercovou velikostí vstupu (například vnořené smyčky).
  • O(log n) – logaritmický čas: Výkon se pomalu zvětšuje, protože se zvyšuje vstup (například binární vyhledávání).

Složitost prostoru měří růst využití paměti s velikostí vstupu pomocí stejného zápisu Big O. Pochopení těchto konceptů pomáhá identifikovat, proč se určité vzory kódu stanou kritickými body ve velkém měřítku.

Rozpoznávání vzorů výkonu a antivzorů

Mezi běžné anti-vzory výkonu patří:

  • Problémy s dotazem N+1: Vytvoření jednoho dotazu pro získání seznamu a pak N více dotazů na související data.
  • Neefektivní datové struktury: Používání seznamů pro časté vyhledávání místo slovníků nebo hashových množin.
  • Předčasné zřetězení řetězců: Vytváření velkých řetězců pomocí += namísto StringBuilder.
  • Synchronní operace: Blokování vláken s synchronními vstupně-výstupními operacemi, pokud by byly lepší asynchronní operace.

Strategie ukládání do mezipaměti můžou výrazně zvýšit výkon ukládáním často přístupných dat do paměti, aby nedocházelo k nákladným výpočtům nebo vstupně-výstupním operacím.

Od jednoduchých až po sofistikované jsou nástroje a metody pro měření výkonu:

Ruční časování pomocí stopek

Ruční instrumentace kódu pomocí Stopwatch nebo dokonce DateTime.UtcNow rozdílů může poskytnout rychlý přehled. Tento přístup je ad hoc, ale přístupný a často dostatečný pro první šetření. Protokoly časování můžete rozprašovat kolem oddílů kódu (například protokolování doby trvání databázového dotazu, jak dlouho trvalo parsování souborů atd.).

Protokolování a čítače

Protokolování je nahodilé, ale dostupné a často postačující pro první analýzu. Protokoly časování můžete rozprašovat kolem oddílů kódu (například protokolování doby trvání databázového dotazu, jak dlouho trvalo parsování souborů atd.).

Protokolování událostí a metriky výkonu

Přidání strategického protokolování může odhalit vzory výkonu a pomoct identifikovat kritické body:

  • Počty operací: Zaznamenávejte, kolik položek bylo zpracováno, kolik databázových dotazů bylo spuštěno, nebo kolik mezipaměťových zásahů/neúspěchů nastalo.
  • Rozpisy časování: Změřte různé fáze operací (například Dotaz databáze: 50 milisekund, Zpracování: 20 milisekund, Serializace: 10 milisekund).
  • Využití prostředků: Sledování přidělení paměti, využití fondu vláken nebo metrik fondu připojení
  • Metriky výkonu: Monitorujte latenci (dobu odezvy), propustnost (operace za sekundu) a charakteristiky škálovatelnosti.

Pokud se například v protokolu zobrazuje "Načteno 1000 záznamů z databáze", když očekáváte, že se zobrazí 100 záznamů, může nesrovnalosti znamenat problém s dotazem N+1 nebo neefektivní logiku dotazu. Podobně může logování rychlosti zásahu mezipaměti pomoci vyhodnotit účinnost strategie ukládání do mezipaměti.

Integrované profilátory (diagnostika sady Visual Studio)

Visual Studio (edice Enterprise a do určité míry Community) obsahuje nástroje pro profilaci (využití procesoru, využití paměti, profiler výkonu). Aplikaci můžete spustit pod profilerem a zobrazí se například rozpis času procesoru podle funkce nebo seznam objektů v haldě a jejich přidělení.

  • Profiler procesoru obvykle vytváří strom volání, kde můžete zjistit, které metody spotřebovávají nejvíce času procesoru.
  • Profiler paměti může pořizovat snímky, aby ukázal, jak roste využití paměti a jaké typy objektů zabírají místo.

Použití profileru je často tak jednoduché, jako když kliknete na Spustit diagnostiku a zvolíte typ profilu.

Nástroje .NET CLI

Pro .NET Core a .NET 5+ poskytuje Microsoft nástroje příkazového řádku, jako je dotnet-counters, dotnet-trace, dotnet-dump, dotnet-gcdump.

  • dotnet-counters může zobrazit metriky výkonu spuštěné aplikace v reálném čase (GC kolekce, výjimky, využití fondu vláken, atd.).
  • dotnet-trace může shromáždit trasování běhu aplikace a analyzovat, které metody byly spuštěny.

Jedná se o pokročilé nástroje, které jsou však neocenitelné pro detailní analýzy nebo profilování produkce, kde nemůžete použít profilér GUI.

Knihovny mikrobenchmarkingu

Pokud chcete porovnat dvě implementace funkce (například původní vs. navrženou optimalizovanou verzi) přísným způsobem, je BenchmarkDotNet oblíbenou knihovnou. Spouští funkce mnohokrát, zahřeje kompilátor za běhu, přesně měří a poskytuje statistiky (například střední, směrodatná odchylka). Tato data se používají pro mikro benchmarky (malé izolované cesty kódu) s vysokou přesností.

Testy výkonu a zatížení

Pro větší scénáře (webové aplikace, služby) můžete napsat zátěžový test nebo použít nástroj (například JMeter, k6 nebo zátěžový test sady Visual Studio) k simulaci mnoha požadavků nebo velkých vstupů. Tento přístup může odhalit propustnost a stabilitu pod stresem a pomáhá identifikovat kritické body, které se zobrazují pouze ve velkém měřítku.

Monitorování systému

Monitorování celkového chování systému za účelem identifikace kritických bodů prostředků a problémů se škálovatelností:

  • Vzory využití procesoru: Vysoké využití procesoru může znamenat algoritmické nekompicience nebo výpočetní kritické body.
  • Spotřeba paměti: Rostoucí využití paměti může naznačovat nevracení paměti, neefektivní datové struktury nebo nadměrné přidělení objektů.
  • V/V metriky: Vysoké vstupně-výstupní operace disku nebo sítě můžou indikovat neefektivní vzorce přístupu k datům nebo špatné ukládání do mezipaměti.
  • Uvolňování paměti: Časté uvolňování paměti může mít vliv na výkon, zejména v aplikacích s vysokou propustností.

Nástroje, jako je Správce úloh nebo PerfMon ve Windows, dotnet-countersmůžou poskytovat přehledy na úrovni systému. Pochopení těchto metrik pomáhá korelovat výkon na úrovni kódu s využitím systémových prostředků.

Monitorujte vylepšení a zabraňte regresím

Po vytvoření směrného plánu můžete začít implementovat vylepšení kódu. Vylepšení kódu by měla být provedena v cyklu aktualizací kódu následovaných měřením, aby se zjistilo, jestli změna měla požadovaný účinek.

Když provedete změnu, vždy znovu přeměřte pomocí stejné metody jako váš základ. Porovnejte nová měření se směrnými hodnotami.

Tady je několik doporučených pokynů pro tento proces:

  • Změňte jednu věc najednou (pokud je to možné): Takže víte, co způsobilo nějaké zlepšení nebo regresi.

  • Spusťte stejný test nebo měření jako referenční: Použijte stejný postup a porovnejte metriky přímo.

  • Pokud bylo dosaženo zlepšení: Výborně, zvažte, jestli splňuje cíl. Pokud jste potřebovali 2x zrychlení a máte 1,5x, můžete iterovat dále.

  • Pokud žádné zlepšení nebo horší výkon: Prozkoumejte proč. Může se stát, že vaše změna neřešila skutečné úzké hrdlo nebo vyměnila jeden náklad za jiný.

  • Zkontrolujte vedlejší účinky: Vedlejší účinky můžou zahrnovat neočekávané výsledky nebo problémy s výkonem. Můžete například optimalizovat výkon procesoru, ale můžete mít velký nárůst využití paměti. Je tento vedlejší účinek přijatelný?

  • Automatizace detekce regrese: Zápis testů jednotek výkonu nebo srovnávacích testů, které se spouští před a po změnách, je běžnou technikou detekce regrese. I když ne každý tým zapisuje testy výkonnosti, není vhodné mít malou sadu testů výkonnosti (zejména pro kritické cesty), které zajistí, aby nové změny výrazně nezpomalovaly.

Použití GitHub Copilotu k usnadnění měření

GitHub Copilot vám může pomoct nastavit techniky měření výkonu:

  • Ptejte se příkladů časování: "Jak můžu měřit dobu provádění metody jazyka C#?" GitHub Copilot může navrhnout Stopwatch pomocí ukázkového kódu.
  • Získejte pokyny k profilaci: Jaké nástroje můžu použít k profilaci aplikace .NET? GitHub Copilot může vypsat nástroje pro profilaci a jejich případy použití.
  • Generovat srovnávací kód: GitHub Copilot může pomoct vytvořit třídy BenchmarkDotNet nebo jiné měřicí nástroje.

GitHub Copilot slouží jako rychlá reference k implementaci technik měření správně.

Shrnutí

Měření výkonu kódu a stanovení směrných plánů je důležitým krokem v procesu optimalizace. Díky systematicky zachytávání času provádění, využití prostředků a charakteristik škálovatelnosti vytvoříte referenční bod pro vyhodnocení dopadu změn. Použití různých metod měření – od jednoduchého načasování Stopwatch až po pokročilé nástroje pro profilaci – umožňuje přesně identifikovat kritické body. Vždy se ujistěte, že vylepšení výkonu neohrožuje správnost nebo zavádějí nové problémy. Díky disciplínovanému přístupu k měření a iteraci můžete efektivně vylepšit výkon kódu při zachování jeho integrity.