Sdílet prostřednictvím


Tipy pro zlepšení časově kritického kódu

Psaní rychlého kódu vyžaduje pochopení všech aspektů aplikace a způsobu interakce se systémem. Tento článek navrhuje alternativy některých z jasnějších technik kódování, které vám pomůžou zajistit, aby časově kritické části kódu fungovaly uspokojivě.

Pokud chcete shrnout, vylepšení časového kritického kódu vyžaduje, abyste:

  • Zjistěte, které části programu musí být rychlé.

  • Znáte velikost a rychlost kódu.

  • Znáte náklady na nové funkce.

  • Znát minimální potřebnou práci k provedení úlohy.

Ke shromažďování informací o výkonu kódu můžete použít sledování výkonu (perfmon.exe).

Oddíly v tomto článku

Ukládání chyb z mezipaměti a chyb stránek

Zmeškané přístupy do mezipaměti, v interní i externí mezipaměti a také chyby stránky (v sekundárním úložišti pro pokyny a data programu) zpomalují výkon programu.

Dosažení mezipaměti procesoru může stát program 10–20 hodinových cyklů. Dosažení externí mezipaměti může stát 20 až 40 hodinových cyklů. Chyba stránky může stát milion hodinových cyklů (za předpokladu, že procesor zpracovává 500 milionů instrukcí za sekundu a čas 2 milisekundy pro chybu stránky). Proto je v nejlepším zájmu spuštění programu napsat kód, který sníží počet neúspěšných přístupů do mezipaměti a chyb stránek.

Jedním z důvodů pomalých programů je, že zabírají více chyb stránky nebo zmeškají mezipaměť častěji, než je potřeba. Abyste se tomuto problému vyhnuli, je důležité používat datové struktury s dobrou lokalitou odkazu, což znamená, že související věci jsou pohromadě. Někdy se datová struktura, která vypadá skvěle, ukázalo, že je hrozná kvůli špatné lokalitě odkazu, a někdy je opačná pravda. Zde jsou dva příklady:

  • Dynamicky přidělené propojené seznamy můžou snížit výkon programu. Když hledáte položku nebo když procházíte seznamem na konec, může každý přeskočený odkaz vynechat mezipaměť nebo způsobit chybu stránky. Implementace seznamu založená na jednoduchých polích může být rychlejší kvůli lepšímu ukládání do mezipaměti a menšímu počtu chyb stránky. I kdybyste umožnili růst pole, může být stále rychlejší.

  • Tabulky hash, které používají dynamicky přidělené propojené seznamy, můžou snížit výkon. V rozšíření můžou tabulky hash, které používají dynamicky přidělené propojené seznamy k ukládání jejich obsahu, výrazně horší. Ve skutečnosti může být v závěrečné analýze jednoduché lineární vyhledávání prostřednictvím pole ve skutečnosti rychlejší (v závislosti na okolnostech). Použití hash tabulky založené na poli (tzv. "uzavřené hashování") je často přehlédnutá implementace, která má často vynikající výkon.

Řazení a vyhledávání

Řazení je v porovnání s mnoha typickými operacemi ze své podstaty časově náročné. Nejlepším způsobem, jak se vyhnout zbytečnému zpomalení, je vyhnout se řazení v kritických časech. Možná budete moct:

  • Odložit řazení až do kritické doby, která není výkonná.

  • Seřaďte data v dřívější době, která není kritická pro výkon.

  • Seřaďte jenom část dat, která skutečně potřebují řazení.

Někdy můžete seznam sestavit v seřazeném pořadí. Buďte opatrní, protože pokud potřebujete vložit data do seřazeného pořadí, můžete vyžadovat složitější datovou strukturu s špatnou lokalitou odkazu, což vede k chybám v mezipaměti a chybám stránek. Neexistuje žádný přístup, který by fungoval ve všech případech. Vyzkoušejte několik přístupů a změřte rozdíly.

Tady je několik obecných tipů pro řazení:

  • K minimalizaci chyb použijte řazení akcií.

  • Jakákoli práce, kterou můžete udělat předem, aby se snížila složitost řazení, stojí za to. Pokud jednorázový průchod dat zjednodušuje porovnávání a snižuje řazení od O(n log n) na O(n), téměř jistě vyrazíme dopředu.

  • Zamyslete se nad umístěním odkazu algoritmu řazení a dat, u kterých očekáváte, že se budou spouštět.

Existuje méně alternativ pro hledání než řazení. Pokud je hledání kritické pro čas, je vyhledávání binárního vyhledávání nebo hash tabulky téměř vždy nejlepší, ale stejně jako při řazení je třeba mít na paměti lokalitu. Lineární vyhledávání prostřednictvím malého pole může být rychlejší než binární vyhledávání prostřednictvím datové struktury s mnoha ukazateli, které způsobují chyby stránky nebo neúspěšné ukládání do mezipaměti.

KNIHOVNY MFC a knihovny tříd

Třídy Microsoft Foundation (MFC) můžou výrazně zjednodušit psaní kódu. Při psaní časového kritického kódu byste měli vědět o režii, která je součástí některých tříd. Prozkoumejte kód MFC, který váš kód kritický pro čas používá, a zjistěte, jestli splňuje vaše požadavky na výkon. Následující seznam identifikuje třídy a funkce MFC, o nichž byste měli vědět:

  • CString MFC volá knihovnu runtime jazyka C, aby dynamicky přidělila paměť CString . Obecně řečeno, CString je stejně efektivní jako jakýkoli jiný dynamicky přidělený řetězec. Stejně jako u jakéhokoli dynamicky přiděleného řetězce má režii dynamického přidělování a vydávání. char Jednoduché pole v zásobníku může často sloužit stejnému účelu a je rychlejší. Nepoužívejte CString k ukládání konstantního řetězce. Místo toho použijte const char *. Jakákoli operace, kterou provádíte s objektem CString , má určitou režii. Použití řetězcových funkcí knihovny za běhu může být rychlejší.

  • CArray Poskytuje CArray flexibilitu, že běžné pole není, ale váš program to nemusí potřebovat. Pokud znáte konkrétní omezení pole, můžete místo toho použít globální pevné pole. Pokud použijete CArray, použijte CArray::SetSize k vytvoření jeho velikosti a určit počet prvků, o které roste, když je nutná skutečná poloha. V opačném případě může přidání prvků způsobit, že pole bude často relokováno a zkopírováno, což je neefektivní a může fragmentovat paměť. Pokud také vložíte položku do pole, CArray přesune další položky v paměti a může být nutné zvětšit matici. Tyto akce můžou způsobit chyby v mezipaměti a chyby stránky. Pokud se podíváte na kód, který mfc používá, můžete vidět, že můžete napsat něco konkrétnějšího pro váš scénář, aby se zlepšil výkon. Vzhledem k tomu CArray , že je šablona, můžete například poskytnout CArray specializace pro konkrétní typy.

  • CListCList je doubly propojený seznam, takže vložení prvku je rychlé na hlavu, ocas a na známé pozici (POSITION) v seznamu. Vyhledání prvku podle hodnoty nebo indexu vyžaduje sekvenční vyhledávání, které ale může být pomalé, pokud je seznam dlouhý. Pokud váš kód nevyžaduje dobly propojený seznam, možná budete chtít znovu zvážit použití CList. Použití ingly propojeného seznamu ukládá režii při aktualizaci jiného ukazatele pro všechny operace a paměť pro tento ukazatel. Nadbytečná paměť není velká, ale je to další příležitost pro neúspěšné ukládání do mezipaměti nebo chyby stránky.

  • IsKindOf Tato funkce může generovat mnoho volání a může přistupovat k paměti v různých datových oblastech, což vede k chybné lokalitě odkazu. Je užitečné pro ladicí sestavení (například ve volání ASSERT), ale zkuste se ho vyhnout v buildu vydané verze.

  • PreTranslateMessage Použijte PreTranslateMessage , když určitý strom oken potřebuje různé akcelerátory klávesnice nebo když do čerpadla zpráv musíte vložit zpracování zpráv. PreTranslateMessage mění zprávy odesílající knihovny MFC. Pokud přepíšete PreTranslateMessage, udělejte to jenom na požadované úrovni. Například není nutné přepsat CMainFrame::PreTranslateMessage , pokud vás zajímají jenom zprávy, které přejdou do podřízených položek určitého zobrazení. Místo toho přepsat PreTranslateMessage třídu zobrazení.

    Nepoužívejte normální cestu odeslání pomocí zpracování PreTranslateMessage všech zpráv odeslaných do libovolného okna. Pro tento účel použijte procedury oken a mapy zpráv MFC.

  • OnIdle Nečinné události se můžou objevit v době, kdy neočekáváte, například mezi WM_KEYDOWN událostmi a WM_KEYUP událostmi. Časovače můžou být efektivnějším způsobem aktivace kódu. Nevynucujte OnIdle opakované zavolání generováním falešných OnIdlezpráv nebo vždy vrácením TRUE z přepsání , které by nikdy neumožňovalo vašemu vláknu spát. Opět může být vhodnější časovač nebo samostatné vlákno.

Sdílené knihovny

Opakované použití kódu je žádoucí. Pokud ale budete používat kód někoho jiného, měli byste se ujistit, že přesně víte, co dělá v takových případech, kdy je pro vás výkon důležitý. Nejlepší způsob, jak to pochopit, je procházením zdrojového kódu nebo měřením pomocí nástrojů, jako je PView nebo Sledování výkonu.

Hromady

Používejte více hald s uvážením. Další haldy vytvořené pomocí HeapCreate a HeapAlloc umožňují spravovat a pak odstranit související sadu přidělení. Neposouvejte příliš mnoho paměti. Pokud používáte více hald, věnujte zvláštní pozornost množství paměti, která je původně potvrzena.

Místo několika hald můžete použít pomocné funkce k rozhraní mezi kódem a výchozí haldou. Pomocné funkce usnadňují vlastní strategie přidělování, které můžou zlepšit výkon vaší aplikace. Pokud například často provádíte malé přidělení, můžete tyto přidělení lokalizovat do jedné části výchozí haldy. Můžete přidělit velký blok paměti a pak použít pomocnou funkci k suballokaci z tohoto bloku. Pak nebudete mít více hald s nevyužitou pamětí, protože přidělení přichází z výchozí haldy.

V některých případech ale použití výchozí haldy může snížit umístění odkazu. Pomocí nástroje Process Viewer, Spy++ nebo Sledování výkonu změřte efekty přesouvání objektů z haldy do haldy.

Změřte haldy, abyste mohli počítat s každým přidělením haldy. K vytvoření kontrolního bodu a výpisu haldy použijte rutiny haldy za běhu jazyka C. Výstup si můžete přečíst do tabulkového programu, jako je Microsoft Excel, a zobrazit výsledky pomocí kontingenčních tabulek. Všimněte si celkového počtu, velikosti a rozdělení přidělení. Porovnejte tyto výsledky s velikostí pracovních sad. Podívejte se také na clustering objektů souvisejících velikostí.

K monitorování využití paměti můžete použít také čítače výkonu.

Vlákna

U úloh na pozadí může být efektivní zpracování nečinných událostí rychlejší než použití vláken. Umístění odkazu je snazší pochopit v jednom vlákně programu.

Dobrým pravidlem je použít vlákno pouze v případě, že oznámení operačního systému, které blokujete, je v kořenovém adresáři práce na pozadí. Vlákna jsou v takovém případě nejlepším řešením, protože je nepraktické blokovat hlavní vlákno události.

Vlákna také představují problémy s komunikací. Komunikační propojení mezi vlákny musíte spravovat se seznamem zpráv nebo přidělováním a používáním sdílené paměti. Správa komunikačního propojení obvykle vyžaduje synchronizaci, aby nedocházelo k podmínkám časování a problémům se vzájemným zablokováním. Tato složitost se může snadno změnit na chyby a problémy s výkonem.

Další informace naleznete v tématu Zpracování smyčky nečinnosti a multithreading.

Malá pracovní sada

Menší pracovní sady znamenají lepší umístění odkazu, méně chyb stránky a více přístupů do mezipaměti. Pracovní sada procesu je nejbližší metrika, která operační systém přímo poskytuje pro měření umístění odkazu.

  • Chcete-li nastavit horní a dolní limity pracovní sady, použijte SetProcessWorkingSetSize.

  • Chcete-li získat horní a dolní limity pracovní sady, použijte GetProcessWorkingSetSize.

  • Chcete-li zobrazit velikost pracovní sady, použijte nástroj Spy++.

Viz také

Optimalizace kódu