Sdílet prostřednictvím


Případová studie: Průvodce začátečníkem pro optimalizaci kódu a snížení nákladů na výpočetní prostředky (C#, Visual Basic, C++, F#)

Optimalizace kódu snižuje čas a náklady na výpočetní prostředky. Tato případová studie ukazuje, jak pomocí nástrojů pro profilaci sady Visual Studio identifikovat a opravit problémy s výkonem v ukázkové aplikaci .NET. Pokud chcete porovnat nástroje pro profilaci, podívejte se na Který nástroj mám zvolit?

Tento průvodce popisuje:

  • Použití nástrojů pro profilaci sady Visual Studio k analýze a zlepšení výkonu
  • Praktické strategie optimalizace využití procesoru, přidělení paměti a interakcí databáze.

Tyto techniky použijte, aby byly vaše vlastní aplikace efektivnější.

Případová studie optimalizace

Ukázková aplikace .NET spouští dotazy na databázi SQLite blogů a příspěvků pomocí Entity Frameworku. Provádí mnoho dotazů a simuluje scénář načítání dat z reálného světa. Aplikace je založená na úvodní ukázce Entity Frameworku, ale používá větší datovou sadu.

Mezi klíčové problémy s výkonem patří:

  • Vysoké využití procesoru: Neefektivní výpočty nebo úlohy zpracování zvyšují spotřebu procesoru a náklady.
  • Neefektivní přidělení paměti: Špatná správa paměti vede k nadměrnému uvolňování paměti a snížení výkonu.
  • Náklady databáze: Neefektivní dotazy a nadměrné volání databáze snižují výkon.

Tato případová studie používá nástroje pro profilaci sady Visual Studio k určení a řešení těchto problémů, jejichž cílem je efektivnější a nákladově efektivnější aplikace.

Výzva

Řešení těchto problémů s výkonem zahrnuje několik problémů:

  • Diagnostika kritických bodů: Identifikace původních příčin vysokého zatížení procesoru, paměti nebo databáze vyžaduje efektivní použití nástrojů pro profilaci a správnou interpretaci výsledků.
  • Omezení znalostí a prostředků: Profilace a optimalizace vyžadují specifické dovednosti a zkušenosti, které nemusí být vždy dostupné.

Strategický přístup, který kombinuje nástroje pro profilaci, technické znalosti a pečlivé testování, je nezbytné překonat tyto výzvy.

Strategie

Tady je základní pohled na přístup v této případové studii:

Shromažďování dat vyžaduje následující úlohy:

  • Nastavte aplikaci na vydání buildu.
  • Vyberte nástroj Využití procesoru v profileru výkonu (Alt+F2).
  • V Profileru výkonu spusťte aplikaci a shromážděte sledování.

Kontrola oblastí vysokého využití procesoru

Po shromáždění trasování pomocí nástroje Využití procesoru a jeho načtení do sady Visual Studio nejprve zkontrolujeme počáteční stránku sestavy .diagsession, která zobrazuje souhrnná data. V sestavě použijte odkaz Otevřít podrobnosti.

Snímek obrazovky s otevřením podrobností v nástroji pro využití CPU

V zobrazení podrobností sestavy otevřete zobrazení stromu volání. Cesta kódu s nejvyšším využitím CPU v aplikaci se nazývá hot path. Ikona plamene horké cesty (Snímek obrazovky s ikonou Horká cesta.) vám může pomoct rychle identifikovat problémy s výkonem, které by mohly být vylepšeny.

V zobrazení stromu volání můžete vidět vysoké využití procesoru pro metodu GetBlogTitleX v aplikaci pomocí přibližně 60% sdílení využití procesoru aplikace. Hodnota Self CPU pro GetBlogTitleX je však nízká, pouze přibližně 0,10%. Na rozdíl od total cpuhodnota self CPU vylučuje čas strávený v jiných funkcích, takže víme, že se podíváme dál dolů stromu volání pro skutečný kritický bod.

snímek obrazovky se stromem volání v nástroji Využití procesoru

GetBlogTitleX provádí externí volání dvou knihoven LINQ DLL, které využívají většinu času procesoru, což je patrné z velmi vysokých hodnot vlastního CPU. Toto je první vodítko, že dotaz LINQ může být oblastí pro optimalizaci.

snímek obrazovky se stromem volání v nástroji Využití procesoru se zvýrazněným vlastním procesorem

Pokud chcete získat vizualizovaný strom volání a jiné zobrazení dat, otevřete zobrazení Flame Graph. (Nebo klikněte pravým tlačítkem na GetBlogTitleX a zvolte View in Flame Graph.) Opět to vypadá, že metoda GetBlogTitleX odpovídá za velké množství využití procesoru aplikace (je znázorněno žlutě). Externí volání knihoven LINQ DLL se zobrazí pod polem GetBlogTitleX a používají pro metodu veškerý čas procesoru.

snímek obrazovky se zobrazením Flame Graph v nástroji Využití procesoru

Shromáždění dalších dat

Další nástroje můžou často poskytovat další informace, které vám pomůžou s analýzou a izolovat problém. V této případové studii používáme následující přístup:

  • Nejprve se podívejte na využití paměti. Může existovat korelace mezi vysokým využitím procesoru a vysokým využitím paměti, takže může být užitečné se podívat na obojí, abyste problém izolovali.
  • Vzhledem k tomu, že jsme identifikovali knihovny DLL LINQ, podíváme se také na databázový nástroj.

Kontrola využití paměti

Abychom zjistili, co se s aplikací děje z hlediska využití paměti, shromažďujeme trasování pomocí nástroje pro přidělování objektů .NET (pro C++, místo toho můžete použít nástroj Využití paměti). Přehled stromu volání ve sledování paměti ukazuje hlavní cestu a pomáhá nám identifikovat oblast s vysokým využitím paměti. Není překvapením v tomto okamžiku, že GetBlogTitleX metoda generuje velké množství objektů! Ve skutečnosti bylo přiděleno více než 900 000 objektů.

snímek obrazovky se stromem volání v nástroji pro přidělování objektů .NET

Většina vytvořených objektů je řetězce, pole objektů a int32. Možná uvidíme, jak se tyto typy generují prozkoumáním zdrojového kódu.

Kontrola dotazu v nástroji Databáze

V profileru výkonu vybereme nástroj Databáze místo využití procesoru (nebo obojí). Když jsme shromáždili trasování, otevřete na stránce diagnostiky kartu Dotazy. Na kartě Dotazy pro trasování databáze uvidíte, že první řádek ukazuje nejdelší dotaz, 2446 ms. Sloupec Záznamy ukazuje, kolik záznamů dotaz načte. Tyto informace můžete použít k pozdějšímu porovnání.

snímek obrazovky s databázovými dotazy v nástroji Databáze

Prozkoumáním příkazu SELECT vygenerovaného jazykem LINQ ve sloupci Query identifikujeme první řádek jako dotaz přidružený k metodě GetBlogTitleX. Pokud chcete zobrazit celý řetězec dotazu, rozbalte šířku sloupce. Úplný řetězec dotazu je:

SELECT "b"."Url", "b"."BlogId", "p"."PostId", "p"."Author", "p"."BlogId", "p"."Content", "p"."Date", "p"."MetaData", "p"."Title"
FROM "Blogs" AS "b" LEFT JOIN "Posts" AS "p" ON "b"."BlogId" = "p"."BlogId" ORDER BY "b"."BlogId"

Všimněte si, že aplikace tady načítá hodně hodnot sloupců, možná více, než potřebujeme. Pojďme se podívat na zdrojový kód.

Optimalizace kódu

Je čas se podívat na zdrojový kód GetBlogTitleX. V nástroji Databáze klikněte pravým tlačítkem myši na dotaz a zvolte Přejít na zdrojový soubor. Ve zdrojovém kódu pro GetBlogTitleXnajdeme následující kód, který ke čtení databáze používá LINQ.

foreach (var blog in db.Blogs.Select(b => new { b.Url, b.Posts }).ToList())
  {
    foreach (var post in blog.Posts)
    {
      if (post.Author == "Fred Smith")
      {
        Console.WriteLine($"Post: {post.Title}");
      }
  }
}

Tento kód používá smyčky foreach k vyhledávání v databázi pro všechny blogy s "Fred Smith" jako autor. Když se na to podíváte, můžete vidět, že se v paměti generuje hodně objektů: nové pole objektů pro každý blog v databázi, přidružené řetězce pro každou adresu URL a hodnoty vlastností obsažených v příspěvcích, jako je ID blogu.

Provedeme trochu průzkumu a najdeme několik běžných doporučení pro optimalizaci dotazů LINQ. Alternativně můžeme ušetřit čas a nechat Copilot udělat výzkum za nás.

Pokud používáme Copilot, v místní nabídce vybereme Ask Copilot a zadáme následující otázku:

Can you make the LINQ query in this method faster?

Spropitné

Lomítkové příkazy, jako je /optimize, můžete využít k formulaci dobrých otázek pro Copilot.

V tomto příkladu poskytuje Copilot následující navrhované změny kódu spolu s vysvětlením.

public void GetBlogTitleX()
{
    var posts = db.Posts
        .Where(post => post.Author == "Fred Smith")
        .Select(post => post.Title)
        .ToList();

    foreach (var postTitle in posts)
    {
        Console.WriteLine($"Post: {postTitle}");
    }
}

Tento kód obsahuje několik změn, které vám pomůžou optimalizovat dotaz:

  • Přidání klauzule Where a odstranění jedné ze smyček foreach.
  • V proměnné Select je promítnuta pouze vlastnost Titul, což je vše, co v tomto příkladu potřebujeme.

V dalším kroku znovu otestujeme pomocí nástrojů pro profilaci.

Výsledky

Po aktualizaci kódu znovu spustíme nástroj Využití procesoru a shromáždíme záznam. Zobrazení stromu volání ukazuje, že GetBlogTitleX běží pouze 1754 ms, využívá 37% celkového procesorového času aplikace, což je významné zlepšení oproti 59%.

snímek obrazovky s vylepšeným využitím procesoru v zobrazení stromu volání nástroje Využití procesoru

Přepněte do zobrazení Flame Graph a zobrazte další vizualizaci znázorňující vylepšení. V tomto zobrazení GetBlogTitleX také používá menší část procesoru.

snímek obrazovky s vylepšeným využitím procesoru v zobrazení Flame Graph nástroje Využití procesoru

Zkontrolujte výsledky v trasování databázového nástroje a pomocí tohoto dotazu se čtou jenom dva záznamy místo 100 000! Dotaz je také hodně zjednodušený a eliminuje nepotřebné funkce LEFT JOIN, která byla vygenerována dříve.

snímek obrazovky s rychlejším časem dotazu v nástroji Databáze

V dalším kroku znovu zkontrolujeme výsledky nástroje pro přidělování objektů .NET a zjistíme, že GetBlogTitleX zodpovídá pouze za 56 000 přidělení objektů, téměř 95% snížení z 900 000!

snímek obrazovky s omezeným přidělením paměti v nástroji pro přidělování objektů .NET

Iterovat

Může být nutné provést několik optimalizací a můžeme pokračovat iterací se změnami kódu, abychom zjistili, které změny zlepšují výkon a pomáhají snížit náklady na výpočetní prostředky.

Další kroky

Následující články a blogové příspěvky obsahují další informace, které vám pomůžou efektivně používat nástroje pro výkon sady Visual Studio.