Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Att skriva snabb kod kräver att du förstår alla aspekter av ditt program och hur det interagerar med systemet. Den här artikeln föreslår alternativ till några av de mer uppenbara kodningstekniker som hjälper dig att se till att de tidskritiska delarna av koden fungerar på ett tillfredsställande sätt.
För att sammanfatta kräver förbättring av tidskritisk kod att du:
Ta reda på vilka delar av programmet som måste vara snabba.
Känna till kodens storlek och hastighet.
Ta reda på kostnaden för nya funktioner.
Känna till det minsta arbete som krävs för att utföra jobbet.
Om du vill samla in information om kodens prestanda kan du använda prestandaövervakaren (perfmon.exe).
Avsnitt i den här artikeln
Cachemissar och sidfel
Missade cacheträffar, både på den interna och externa cachen, samt sidfel (går till sekundär lagring för programinstruktioner och data) saktar ned programmets prestanda.
En CPU-cacheträff kan kosta programmet 10–20 klockcykler. En extern cacheträff kan kosta 20–40 klockcykler. Ett sidfel kan kosta en miljon klockcykler (förutsatt att en processor hanterar 500 miljoner instruktioner/sekund och en tid på 2 millisekunder för ett sidfel). Därför är det bäst för programkörning att skriva kod som minskar antalet missade cacheträffar och sidfel.
En orsak till långsamma program är att de tar fler sidfel eller missar cachen oftare än nödvändigt. För att undvika det här problemet är det viktigt att använda datastrukturer med bra referenslokalitet, vilket innebär att hålla ihop relaterade saker. Ibland visar sig en datastruktur som ser bra ut vara hemsk på grund av dålig referenslokalitet, och ibland är det omvända sant. Nedan följer två exempel:
Dynamiskt allokerade länkade listor kan minska programmets prestanda. När du söker efter ett objekt, eller när du passerar en lista till slutet, kan varje överhoppad länk missa cachen eller orsaka ett sidfel. En listimplementering baserad på enkla matriser kan gå snabbare på grund av bättre cachelagring och färre sidfel. Även om du tar hänsyn till att det skulle vara svårare att utöka matrisen, kan det fortfarande vara snabbare.
Hash-tabeller som använder dynamiskt allokerade länkade listor kan försämra prestanda. I tillägg kan hash-tabeller som använder dynamiskt allokerade länkade listor för att lagra innehållet prestera betydligt sämre. I den slutliga analysen kan en enkel linjär sökning genom en matris faktiskt vara snabbare (beroende på omständigheterna). Användning av en matrisbaserad hash-tabell (så kallad "stängd hashing") är en ofta förbisedd implementering som ofta har överlägsen prestanda.
Sortera och söka
Sortering är i sig tidskrävande jämfört med många vanliga åtgärder. Det bästa sättet att undvika onödig avmattning är att undvika sortering vid kritiska tidpunkter. Du kanske kan:
Skjut upp sortering till en icke-prestandakritisk tid.
Sortera data vid en tidigare, icke-prestandakritisk tid.
Sortera bara den del av data som verkligen behöver sorteras.
Ibland kan du skapa listan i sorterad ordning. Var försiktig, för om du behöver infoga data i sorterad ordning kan du behöva en mer komplicerad datastruktur med dålig referenslokalitet, vilket leder till cachemissar och sidfel. Det finns ingen metod som fungerar i alla fall. Prova flera metoder och mät skillnaderna.
Här följer några allmänna tips för sortering:
Använd en lagersortering för att minimera buggar.
Allt arbete du kan göra i förväg för att minska komplexiteten i sorten är värt besväret. Om en engångsöverföring av dina data förenklar jämförelserna och minskar sorteringen från O(n log n) till O(n) kommer du med största sannolikhet att gå vidare.
Tänk på platsen för referensen för sorteringsalgoritmen och de data som du förväntar dig att den ska köras på.
Det finns färre alternativ för sökningar än för sortering. Om sökningen är tidskritisk är en binär sökning eller hashtabellsökning nästan alltid bäst, men precis som vid sortering måste du ha lokaliteten i åtanke. En linjär sökning genom en liten matris kan vara snabbare än en binär sökning genom en datastruktur med många pekare som orsakar sidfel eller cachemissar.
MFC- och klassbibliotek
Microsoft Foundation-klasserna (MFC) kan förenkla skrivning av kod avsevärt. När du skriver tidskritisk kod bör du vara medveten om de omkostnader som ingår i vissa av klasserna. Granska MFC-koden som din tidskritiska kod använder för att se om den uppfyller dina prestandakrav. Följande lista identifierar MFC-klasser och funktioner som du bör känna till:
CStringMFC anropar C-körningsbiblioteket för att allokera minne för enCStringdynamiskt.CStringGenerellt sett är lika effektivt som alla andra dynamiskt allokerade strängar. Precis som med alla dynamiskt allokerade strängar har den omkostnaderna för dynamisk allokering och frigöring. Ofta kan en enkelcharmatris på stacken tjäna samma syfte och är snabbare. Använd inte enCStringför att lagra en konstant sträng. Användconst char *i stället. Alla åtgärder som du utför med ettCStringobjekt har vissa omkostnader. Det kan gå snabbare att använda körningsbibliotekssträngsfunktionerna .CArrayACArrayger flexibilitet som en vanlig matris inte gör, men programmet kanske inte behöver det. Om du känner till de specifika gränserna för matrisen kan du använda en global fast matris i stället. Om du använderCArray, användCArray::SetSizeför att fastställa dess storlek och specificera antalet element med vilket den växer när en omallokering är nödvändig. Annars kan tillägg av element göra att matrisen ofta omallokeras och kopieras, vilket är ineffektivt och kan fragmentera minnet. Om du infogar ett objekt i en matrisCArrayflyttas även efterföljande objekt i minnet och kan behöva utöka matrisen. Dessa åtgärder kan orsaka cachemissar och sidfel. Om du tittar igenom koden som MFC använder kan du se att du kan skriva något som är mer specifikt för ditt scenario för att förbättra prestandan. EftersomCArrayär en mall kan du till exempel tillhandahållaCArrayspecialiseringar för specifika typer.CListCListär en dubbelt länkad lista, så elementinfogning är snabb i huvudet, svansen och vid en känd position (POSITION) i listan. Att leta upp ett element efter värde eller index kräver dock en sekventiell sökning, vilket kan vara långsamt om listan är lång. Om koden inte kräver en dubbelt länkad lista kanske du vill överväga att användaCList. Genom att använda en enkelt länkad lista sparar man in kostnaden för att uppdatera en annan pekare för alla åtgärder och minnet för den pekaren. Det extra minnet är inte stort, men det är en annan möjlighet för cachemissar eller sidfel.IsKindOfDen här funktionen kan generera många anrop och kan komma åt minne i olika dataområden, vilket leder till felaktig plats för referens. Det är användbart för en felsökningsversion (till exempel i ett ASSERT-anrop), men försök att undvika att använda den i en versionsversion.PreTranslateMessageAnvändPreTranslateMessagenär ett visst fönsterträd behöver olika tangentbordsacceleratorer eller när du måste infoga meddelandehantering i meddelandepumpen.PreTranslateMessageändrar MFC dispatch-meddelanden. Om du åsidosätterPreTranslateMessagegör du det endast på den nivå som behövs. Det är till exempel inte nödvändigt att åsidosättaCMainFrame::PreTranslateMessageom du bara är intresserad av meddelanden som går till barn i en specifik vy. ÅsidosättPreTranslateMessageför visningsklassen i stället.Kringgå inte den normala sändningssökvägen med hjälp av
PreTranslateMessageför att hantera vilket meddelande som helst som skickas till vilket fönster som helst. Använd fönsterprocedurer och MFC-meddelandekartor för det ändamålet.OnIdleInaktiva händelser kan inträffa ibland som du inte förväntar dig, till exempel mellanWM_KEYDOWNochWM_KEYUPhändelser. Timers kan vara ett mer effektivt sätt att utlösa din kod. Tvinga inteOnIdleatt anropas upprepade gånger genom att generera falska meddelanden eller genom att alltid returneraTRUEfrån en åsidosättning avOnIdle, vilket aldrig skulle tillåta att tråden kan sova. Återigen kan en timer eller en separat tråd vara lämpligare.
Delade bibliotek
Återanvändning av kod är önskvärt. Men om du ska använda någon annans kod bör du se till att du vet exakt vad den gör i de fall där prestanda är avgörande för dig. Det bästa sättet att förstå det är genom att gå igenom källkoden eller genom att mäta med verktyg som PView eller Prestandaövervakaren.
Högar
Använd flera heaps med diskretion. Ytterligare heaps skapade med HeapCreate och HeapAlloc låter dig hantera och sedan ta bort en relaterad uppsättning av allokeringar. Tilldela inte för mycket minne. Om du använder flera heaps bör du ägna särskild uppmärksamhet åt den mängd minne som ursprungligen har allokerats.
I stället för flera hopar kan du använda hjälpfunktioner för att koppla mellan din kod och standardhop. Hjälpfunktioner underlättar anpassade allokeringsstrategier som kan förbättra programmets prestanda. Om du till exempel ofta utför små allokeringar kanske du vill lokalisera dessa allokeringar till en del av standardhögen. Du kan allokera ett stort minnesblock och sedan använda en hjälpfunktion för att underallokera från det blocket. Sedan har du inte flera högar med oanvänt minne eftersom allokeringen kommer från standardhögen.
I vissa fall kan dock användning av standardhögen minska referenslokaliteten. Använd Process Viewer, Spy++eller Performance Monitor för att mäta effekterna av att flytta objekt från heap till heap.
Mät dina heaps så att du kan ta hänsyn till varje allokering på heapen. Använd felsökningsrutinerna för C-körning för att kontrollera och dumpa din heap. Du kan läsa utdata i ett kalkylbladsprogram som Microsoft Excel och använda pivottabeller för att visa resultatet. Observera det totala antalet, storleken och fördelningen av allokeringar. Jämför dessa resultat med storleken på arbetsset. Titta också på klustring av objekt med liknande storlek.
Du kan också använda prestandaräknarna för att övervaka minnesanvändningen.
Trådar
För bakgrundsuppgifter kan effektiv inaktivitetshantering av händelser vara snabbare än att använda trådar. Det är lättare att förstå referenslokaliteten i ett program med en tråd.
En bra tumregel är att endast använda en tråd om ett operativsystemmeddelande som du blockerar finns i roten för bakgrundsarbetet. Trådar är den bästa lösningen i ett sådant fall eftersom det är opraktiskt att blockera en huvudtråd på en händelse.
Trådarna innehåller även kommunikationsproblem. Du måste hantera kommunikationslänken mellan dina trådar, med en lista över meddelanden eller genom att allokera och använda delat minne. För att hantera kommunikationslänken krävs vanligtvis synkronisering för att undvika konkurrensförhållanden och problem med dödlägen. Den här komplexiteten kan enkelt förvandlas till buggar och prestandaproblem.
Mer information finns i Bearbetning av inaktiva loopar och multitrådning.
Liten arbetsuppsättning
Mindre arbetsuppsättningar innebär bättre referenslokalitet, färre sidfel och fler cacheträffar. Arbetsuppsättningen för processen är det närmaste mått som operativsystemet direkt tillhandahåller för att mäta referenslokaliteten.
Om du vill ange de övre och nedre gränserna för arbetsuppsättningen använder du
SetProcessWorkingSetSize.Om du vill hämta de övre och nedre gränserna för arbetsuppsättningen använder du
GetProcessWorkingSetSize.Om du vill visa storleken på arbetsuppsättningen använder du Spy++.