Dela via


Fallstudie: Nybörjarguide för att optimera kod och minska beräkningskostnader (C#, Visual Basic, C++, F#)

Om du optimerar koden minskar beräkningstiden och kostnaderna. Den här fallstudien visar hur du använder Visual Studio-profileringsverktyg för att identifiera och åtgärda prestandaproblem i ett .NET-exempelprogram. Om du vill jämföra profileringsverktyg kan du läsa Vilket verktyg ska jag välja?

Den här guiden beskriver:

  • Så här använder du Visual Studio-profileringsverktyg för att analysera och förbättra prestanda.
  • Praktiska strategier för att optimera CPU-användning, minnesallokering och databasinteraktioner.

Använd dessa tekniker för att göra dina egna program mer effektiva.

Fallstudie om optimering

.NET-exempelprogrammet kör frågor mot en SQLite-databas med bloggar och inlägg med Entity Framework. Den kör många frågor och simulerar ett verkligt datahämtningsscenario. Appen baseras på exempel på hur Entity Framework kommer igång, men använder en större datauppsättning.

Viktiga prestandaproblem är:

  • Hög CPU-användning: Ineffektiva beräkningar eller bearbetningsuppgifter ökar CPU-förbrukningen och kostnaderna.
  • Ineffektiv minnesallokering: Dålig minneshantering leder till överdriven skräpinsamling och lägre prestanda.
  • Databasomkostnader: Ineffektiva frågor och överdrivna databasanrop försämrar prestandan.

Den här fallstudien använder Visual Studio-profileringsverktyg för att hitta och åtgärda dessa problem, i syfte att göra programmet mer effektivt och kostnadseffektivt.

Utmaning

Att åtgärda dessa prestandaproblem innebär flera utmaningar:

  • Diagnostisera flaskhalsar: För att identifiera rotorsaker till höga processor-, minnes- eller databaskostnader krävs effektiv användning av profileringsverktyg och en korrekt tolkning av resultaten.
  • Kunskaps- och resursbegränsningar: Profilering och optimering kräver specifika kunskaper och erfarenheter, som kanske inte alltid är tillgängliga.

En strategisk metod för att kombinera profileringsverktyg, teknisk kunskap och noggrann testning är avgörande för att lösa dessa utmaningar.

Strategi

Här är en översikt över metoden i den här fallstudien:

  • Börja med en cpu-användningsspårning med hjälp av Visual Studio cpu-användningsverktyget. Visual Studios processoranvändningsverktyg är en bra utgångspunkt för prestandaundersökningar.
  • Samla in ytterligare spårningar för minnes- och databasanalys:

Datainsamling kräver följande uppgifter:

  • Ställ in appen på släppversion.
  • Välj verktyget CPU-användning i Performance Profiler (Alt+F2).
  • I Prestandaprofiler, starta appen och registrera en spårlogg.

Inspektera områden med hög CPU-användning

När du har samlat in en spårning med verktyget CPU-användning och läst in den i Visual Studio kontrollerar vi först den första .diagsession rapportsida som visar sammanfattade data. Använd länken Öppna detaljer i rapporten.

Skärmbild av hur du öppnar information i verktyget CPU-användning.

I vyn för rapportdetaljer öppnar du Samtalsträd. Kodsökvägen med högsta CPU-användning i appen kallas frekvent sökväg. Flamikonen för frekvent sökväg (Skärmbild som visar ikonen För frekvent sökväg.) kan hjälpa dig att snabbt identifiera prestandaproblem som kan förbättras.

I vyn Samtalsträd kan du se hög CPU-användning för GetBlogTitleX-metoden i appen med cirka 60% del av appens CPU-användning. Värdet för självprocessor för GetBlogTitleX är dock lågt, endast cirka 10%. Till skillnad från total CPU-exkluderar värdet egen CPU- tid som tillbringas i andra funktioner, så vi vet att vi ska titta längre ned i anropsträdet för att hitta den verkliga flaskhalsen.

Skärmbild av vyn Samtalsträd i verktyget CPU-användning.

GetBlogTitleX gör externa anrop till två LINQ-DLL:er, som använder större delen av CPU-tiden, vilket framgår av de mycket höga Själv-CPU--värden. Det här är den första ledtråden om att en LINQ-fråga kan vara ett område att optimera.

Skärmbild av vyn Samtalsträd i verktyget CPU-användning med Egen CPU markerat.

Om du vill hämta ett visualiserat anropsträd och en annan vy av data öppnar du vyn Flame Graph. (Eller högerklicka på GetBlogTitleX och välj View in Flame Graph.) Här ser det återigen ut som att metoden GetBlogTitleX ansvarar för en stor del av appens CPU-användning (visas i gult). Externa anrop till LINQ-DLL:er visas under rutan GetBlogTitleX, och de använder all CPU-tid för metoden.

Skärmbild av Flame Graph-vyn i verktyget CPU-användning.

Samla in ytterligare data

Ofta kan andra verktyg ge ytterligare information för att hjälpa till med analysen och isolera problemet. I den här fallstudien har vi följande metod:

  • Titta först på minnesanvändningen. Det kan finnas en korrelation mellan hög CPU-användning och hög minnesanvändning, så det kan vara bra att titta på båda för att isolera problemet.
  • Eftersom vi har identifierat LINQ-DLL:er tittar vi också på databasverktyget.

Kontrollera minnesanvändningen

Om du vill se vad som händer med appen när det gäller minnesanvändning samlar vi in en spårning med hjälp av .NET-objektallokeringsverktyget (för C++kan du använda verktyget Minnesanvändning i stället). Vyn Samtalsträd i minnesspårningen visar den heta sökvägen och hjälper oss att identifiera ett område med hög minnesanvändning. Ingen överraskning i det här läget verkar GetBlogTitleX-metoden generera många objekt! Över 900 000 objektallokeringar, faktiskt.

Skärmbild av vyn Samtalsträd i .NET-objektallokeringsverktyget.

De flesta objekt som skapas är strängar, objektmatriser och Int32s. Vi kanske kan se hur dessa typer genereras genom att undersöka källkoden.

Kontrollera frågan i databasverktyget

I Prestandaprofiler väljer vi databasverktyget i stället för CPU-användning (eller väljer båda). När vi har samlat in en spårning öppnar du fliken Frågor på diagnostiksidan. På fliken Frågor för databasspårningen kan du se att den första raden visar den längsta frågan, 2 446 ms. Kolumnen Records visar hur många poster frågan läser. Du kan använda den här informationen för senare jämförelse.

Skärmbild av databasfrågor i databasverktyget.

Genom att undersöka SELECT-instruktionen som genereras av LINQ i kolumnen Fråga identifierar vi den första raden som den fråga som är associerad med metoden GetBlogTitleX. Om du vill visa den fullständiga frågesträngen expanderar du kolumnbredden. Den fullständiga frågesträngen är:

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"

Observera att appen hämtar många kolumnvärden här, kanske mer än vi behöver. Nu ska vi titta på källkoden.

Optimera kod

Det är dags att ta en titt på GetBlogTitleX källkod. Högerklicka på frågan i databasverktyget och välj Gå till källfil. I källkoden för GetBlogTitleXhittar vi följande kod som använder LINQ för att läsa databasen.

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}");
      }
  }
}

Den här koden använder foreach-loopar för att söka i databasen efter bloggar med "Fred Smith" som författare. Om du tittar på det kan du se att många objekt genereras i minnet: en ny objektmatris för varje blogg i databasen, associerade strängar för varje URL och värden för egenskaper som finns i inläggen, till exempel blogg-ID.

Vi gör lite efterforskningar och hittar några vanliga rekommendationer för hur du optimerar LINQ-frågor. Alternativt kan vi spara tid och låta Copilot göra forskningen åt oss.

Om vi använder Copilot väljer vi Fråga Copilot från snabbmenyn och skriver följande fråga:

Can you make the LINQ query in this method faster?

Tips

Du kan använda snedstreckskommandon som /optimize för att skapa bra frågor för Copilot.

I det här exemplet ger Copilot följande föreslagna kodändringar, tillsammans med en förklaring.

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}");
    }
}

Den här koden innehåller flera ändringar som hjälper dig att optimera frågan:

  • Lade till Where-satsen och eliminerade en av foreach-looparna.
  • Projicerade endast egenskapen Title i instruktionen Select, vilket är allt vi behöver i det här exemplet.

Sedan testas vi på nytt med hjälp av profileringsverktygen.

Resultat

När vi har uppdaterat koden kör vi cpu-användningsverktyget igen för att samla in en spårning. Vyn Samtalsträd visar att GetBlogTitleX bara körs 1754 ms, med 37% av appens totala CPU-användning, vilket är en betydande förbättring från 59%.

Skärmbild av förbättrad CPU-användning i Anropsträd-vyn i verktyget CPU Usage.

Växla till vyn Flame Graph för att se en annan visualisering som visar förbättringen. I den här vyn använder GetBlogTitleX också en mindre del av processorn.

Skärmbild av förbättrad CPU-användning i Flame Graph-vyn för verktyget CPU-användning.

Kontrollera resultaten i databasverktyget för spårning, och bara två poster läses med den här frågan i stället för 100 000! Dessutom är frågeställningen mycket förenklad och tar bort den onödiga LEFT JOIN som genererades tidigare.

Skärmbild av snabbare frågetid i databasverktyget.

Därefter kontrollerar vi resultatet i .NET-objektallokeringsverktyget igen och ser att GetBlogTitleX endast ansvarar för 56 000 objektallokeringar, nästan 95% minskning från 900 000!

Skärmbild av minskad minnesallokering i .NET-objektallokeringsverktyget.

Iterera

Flera optimeringar kan vara nödvändiga och vi kan fortsätta att iterera med kodändringar för att se vilka ändringar som förbättrar prestanda och bidrar till att minska beräkningskostnaden.

Nästa steg

Följande artiklar och blogginlägg innehåller mer information som hjälper dig att lära dig att använda Visual Studio-prestandaverktygen effektivt.