Fallstudie: Nybörjarguide för att optimera kod och minska beräkningskostnader (C#, Visual Basic, C++, F#)
Att minska beräkningstiden innebär att minska kostnaderna, så att optimera koden kan spara pengar. I den här fallstudien används ett exempelprogram med prestandaproblem för att visa hur du använder profileringsverktyg för att förbättra effektiviteten. Om du vill jämföra profileringsverktyg kan du läsa Vilket verktyg ska jag välja?
Den här fallstudien beskriver följande ämnen:
- Vikten av kodoptimering och dess inverkan på att minska beräkningskostnaderna.
- Så här använder du Visual Studio-profileringsverktyg för att analysera programprestanda.
- Så här tolkar du de data som tillhandahålls av dessa verktyg för att identifiera prestandaflaskhalsar.
- Använda praktiska strategier för att optimera kod, med fokus på CPU-användning, minnesallokering och databasinteraktioner.
Följ med och tillämpa dessa tekniker på dina egna program för att göra dem mer effektiva och kostnadseffektiva.
Fallstudie om optimering
Exempelprogrammet som granskas i den här fallstudien är ett .NET-program som kör frågor mot en databas med bloggar och blogginlägg. Den använder Entity Framework, en populär ORM (Object-Relational Mapping) för .NET, för att interagera med en lokal SQLite-databas. Programmet är strukturerat för att köra ett stort antal frågor, vilket simulerar ett verkligt scenario där ett .NET-program kan behövas för att hantera omfattande datahämtningsuppgifter. Exempelprogrammet är en modifierad version av Entity Framework-exempel för att komma igång.
Det primära prestandaproblemet med exempelprogrammet ligger i hur det hanterar beräkningsresurser och interagerar med databasen. Programmet har en flaskhals för prestanda som avsevärt påverkar dess effektivitet och därmed de beräkningskostnader som är associerade med att köra det. Problemet omfattar följande symtom:
Hög CPU-användning: Program kan utföra ineffektiva beräkningar eller bearbetningsuppgifter på ett sätt som i onödan förbrukar en stor mängd CPU-resurser. Detta kan leda till långsamma svarstider och ökade driftskostnader.
Ineffektiv minnesallokering: Program kan ibland stöta på problem som rör minnesanvändning och allokering. I .NET-appar kan ineffektiv minneshantering leda till ökad skräpinsamling, vilket i sin tur kan påverka programmets prestanda.
Omkostnader för databasinteraktion: Program som kör ett stort antal frågor mot en databas kan uppleva flaskhalsar relaterade till databasinteraktioner. Detta omfattar ineffektiva frågor, överdrivna databasanrop och dålig användning av Entity Framework-funktioner, som alla kan försämra prestanda.
Fallstudien syftar till att åtgärda dessa problem genom att använda Visual Studios profileringsverktyg för att analysera programmets prestanda. Genom att förstå var och hur programmets prestanda kan förbättras kan utvecklare implementera optimeringar för att minska CPU-användningen, förbättra minnesallokeringseffektiviteten, effektivisera databasinteraktioner och optimera resursanvändningen. Det ultimata målet är att förbättra programmets övergripande prestanda, vilket gör det mer effektivt och kostnadseffektivt att köra.
Utmaning
Att åtgärda prestandaproblemen i .NET-exempelprogrammet innebär flera utmaningar. Dessa utmaningar beror på komplexiteten i att diagnostisera flaskhalsar i prestanda. De viktigaste utmaningarna med att åtgärda de problem som beskrivs är följande:
diagnostisera prestandaflaskhalsar: En av de främsta utmaningarna är att identifiera de grundläggande orsakerna till prestandaproblemen. Hög CPU-användning, ineffektiv minnesallokering och omkostnader för databasinteraktion kan ha flera bidragande faktorer. Utvecklare måste använda profileringsverktyg effektivt för att diagnostisera dessa problem, vilket kräver viss förståelse för hur dessa verktyg fungerar och hur de tolkar sina utdata.
kunskaps- och resursbegränsningar: Slutligen kan teamen stöta på begränsningar som rör kunskap, expertis och resurser. Profilering och optimering av ett program kräver specifika kunskaper och erfarenheter, och alla team kan inte ha omedelbar åtkomst till dessa resurser.
Att hantera dessa utmaningar kräver en strategisk metod som kombinerar effektiv användning av profileringsverktyg, teknisk kunskap och noggrann planering och testning. Fallstudien syftar till att vägleda utvecklare genom den här processen, tillhandahålla strategier och insikter för att övervinna dessa utmaningar och förbättra programmets prestanda.
Strategi
Här är en översikt över metoden i den här fallstudien:
- Vi startar undersökningen genom att ta en cpu-användningsspårning. Visual Studios cpu-användningsverktyg är ofta användbart för att påbörja prestandaundersökningar och optimera kod för att minska kostnaderna.
- För att få ytterligare insikter för att isolera problem eller förbättra prestandan samlar vi sedan in en spårning med hjälp av något av de andra profileringsverktygen. Till exempel:
- Vi tar en titt på minnesanvändningen. För .NET provar vi verktyget .NET-objektallokering först. (För .NET eller C++kan du titta på verktyget Minnesanvändning i stället.)
- För ADO.NET eller Entity Framework kan vi använda verktyget Database för att undersöka SQL-frågor, exakt frågetid med mera.
Datainsamling kräver följande uppgifter:
- Ställa in appen på en släppbyggnad.
- Välj verktyget CPU-användning från Prestandaprofilen (Alt+F2). (Senare steg omfattar några av de andra verktygen.)
- Starta appen från Prestandaprofiler och samla in en spårning.
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.
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 () 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.
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.
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.
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.
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.
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 GetBlogTitleX
hittar 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 avforeach
-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%.
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.
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.
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!
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.
- Fallstudie: Isolera ett prestandaproblem
- fallstudie: Dubbel prestanda på under 30 minuter
- Förbättra Visual Studio-prestanda med det nya instrumenteringsverktyget