Sdílet prostřednictvím


Doporučené postupy optimalizace

Tento dokument popisuje některé osvědčené postupy pro optimalizaci programů C++ v sadě Visual Studio.

Možnosti kompilátoru a linkeru

Optimalizace na základě profilu

Visual Studio podporuje optimalizaci na základě profilu (PGO). Tato optimalizace používá data profilu z trénovacích spuštění instrumentované verze aplikace k pozdější optimalizaci aplikace. Použití PGO může být časově náročné, takže nemusí být něco, co každý vývojář používá, ale doporučujeme použít PGO pro finální vydání buildu produktu. Další informace najdete v tématu Optimalizace s asistencí profilu.

Kromě toho byla vylepšena optimalizace celého programu (také zná generování kódu času propojení) a /O1 /O2 optimalizace. Obecně platí, že aplikace zkompilovaná s jednou z těchto možností bude rychlejší než stejná aplikace zkompilovaná s dřívějším kompilátorem.

Další informace naleznete v tématu /GL (Optimalizace celého programu) a /O1, /O2 (minimalizace velikosti, maximalizace rychlosti).

Jakou úroveň optimalizace se má použít

Pokud je to vůbec možné, finální sestavení vydaných verzí by se měla zkompilovat pomocí optimalizací s asistencí profilu. Pokud není možné sestavovat pomocí PGO, ať už kvůli nedostatečné infrastruktuře pro spouštění instrumentovaných buildů nebo nemáte přístup ke scénářům, doporučujeme sestavovat pomocí optimalizace celého programu.

Přepínač /Gy je také velmi užitečný. Pro každou funkci vygeneruje samostatnou funkci COMDAT, která poskytuje linkeru větší flexibilitu, pokud jde o odebrání neodkazovaných comdatů a skládání COMDAT. Jedinou nevýhodou použití /Gy je, že může způsobovat problémy při ladění. Proto je obecně doporučeno ho používat. Další informace najdete v tématu /Gy (Povolení propojení na úrovni funkce).

Pro propojení v 64bitových prostředích se doporučuje použít možnost linkeru /OPT:REF,ICF a v 32bitových prostředích /OPT:REF se doporučuje. Další informace najdete v tématu /OPT (optimalizace).

Důrazně se také doporučuje generovat symboly ladění, a to i s optimalizovanými buildy vydaných verzí. Nemá vliv na vygenerovaný kód a v případě potřeby usnadňuje ladění aplikace.

Přepínače s plovoucí desetinou čárkou

Byla /Op odebrána možnost kompilátoru a byly přidány následující čtyři možnosti kompilátoru pro optimalizaci s plovoucí desetinnou čárkou:

Možnost Popis
/fp:precise Toto je výchozí doporučení a ve většině případů by se mělo používat.
/fp:fast Doporučuje se, pokud je výkon velmi důležitý, například ve hrách. Výsledkem bude nejrychlejší výkon.
/fp:strict Doporučuje se, pokud jsou požadované přesné výjimky s plovoucí desetinou čárkou a chování IEEE. Výsledkem bude nejpomalejší výkon.
/fp:except[-] Lze použít ve spojení s /fp:strict nebo /fp:precise, ale ne /fp:fast.

Další informace najdete v tématu /fp (Určení chování s plovoucí desetinou čárkou).

Declspecs optimalizace

V této části se podíváme na dvě declspecs, které lze použít v programech k usnadnění výkonu: __declspec(restrict) a __declspec(noalias).

Declspec restrict lze použít pouze u deklarací funkcí, které vracejí ukazatel, například __declspec(restrict) void *malloc(size_t size);

Declspec restrict se používá u funkcí, které vracejí neaualiased ukazatele. Toto klíčové slovo se používá pro implementaci malloc knihovny C-Runtime, protože nikdy nevrátí hodnotu ukazatele, která je již používána v aktuálním programu (pokud neděláte něco neplatného, například použití paměti po uvolnění).

Declspec restrict dává kompilátoru více informací pro provádění optimalizací kompilátoru. Jednou z nejtěžších věcí, kterou kompilátor určí, je to, jaké ukazatele aliasy jiných ukazatelů, a použití těchto informací výrazně pomůže kompilátoru.

Stojí za to upozornit, že to je slib kompilátoru, ne něco, co kompilátor ověří. Pokud váš program používá tento restrict postup nevhodným způsobem, může mít váš program nesprávné chování.

Další informace najdete na webu restrict.

Declspec noalias je také použit pouze pro funkce a označuje, že funkce je poločistá funkce. Poločistá funkce je funkce, která odkazuje nebo upravuje pouze místní hodnoty, argumenty a nepřímé argumenty první úrovně. Tento declspec je příslib kompilátoru, a pokud funkce odkazuje na globální nebo druhé úrovně nepřímých odkazů argumentů ukazatele, kompilátor může vygenerovat kód, který aplikaci přeruší.

Další informace najdete na webu noalias.

Direktivy optimalizace

Pro optimalizaci kódu existuje také několik užitečných pramas. První, o čem budeme diskutovat, je #pragma optimize:

#pragma optimize("{opt-list}", on | off)

Tato direktiva pragma umožňuje nastavit danou úroveň optimalizace podle funkce. To je ideální pro případy, kdy dojde k chybovému ukončení aplikace při kompilaci dané funkce s optimalizací. Tuto možnost můžete použít k vypnutí optimalizací jedné funkce:

#pragma optimize("", off)
int myFunc() {...}
#pragma optimize("", on)

Další informace najdete na webu optimize.

Inlining je jednou z nejdůležitějších optimalizací, které kompilátor provádí, a zde hovoříme o několika pragmatech, které pomáhají toto chování upravit.

#pragma inline_recursion je užitečné pro určení, zda má aplikace být schopná vložit rekurzivní volání. Ve výchozím nastavení je vypnutý. Pro mělké rekurze malých funkcí můžete tuto funkci zapnout. Další informace najdete na webu inline_recursion.

Další užitečnou direktivou pragma pro omezení hloubky inliningu je #pragma inline_depth. To je obvykle užitečné v situacích, kdy se pokoušíte omezit velikost programu nebo funkce. Další informace najdete na webu inline_depth.

__restrict a __assume

V sadě Visual Studio existuje několik klíčových slov, která můžou pomoct s výkonem: __restrict a __assume.

Nejprve je třeba poznamenat, že __restrict a __declspec(restrict) jsou to dvě různé věci. I když jsou poněkud související, jejich sémantika se liší. __restrict je kvalifikátor typu, například const nebo volatile, ale výhradně pro typy ukazatelů.

Ukazatel upravený __restrict pomocí se označuje jako ukazatel __restrict. Ukazatel __restrict je ukazatel, ke kterému lze přistupovat pouze prostřednictvím ukazatele __restrict. Jinými slovy, jiný ukazatel nelze použít pro přístup k datům, na která odkazuje ukazatel __restrict.

__restrict může být výkonným nástrojem pro optimalizátor Microsoft C++, ale používejte ho s velkou opatrností. Pokud se použije nesprávně, optimalizátor může provést optimalizaci, která by přerušovala vaši aplikaci.

Vývojář __assumemůže kompilátoru říct, aby se předpoklady o hodnotě určité proměnné.

Například __assume(a < 5); optimalizátoru říká, že na tomto řádku kódu je proměnná a menší než 5. Znovu je to slib kompilátoru. Pokud a je v tomto okamžiku v programu skutečně 6, pak chování programu po optimalizaci kompilátoru nemusí být to, co byste očekávali. __assume je nejužitečnější před příkazy switch a/nebo podmíněným výrazem.

Existují určitá omezení __assume. Za prvé, stejně jako __restrict, je to pouze návrh, takže kompilátor je zdarma ignorovat. __assume V současné době také funguje pouze s proměnlivými nerovnostmi vůči konstantám. Nerozšířuje symbolické nerovnosti, například předpokládat(a < b).

Vnitřní podpora

Vnitřní funkce jsou volání funkcí, ve kterých kompilátor má vnitřní znalosti o volání, a místo volání funkce v knihovně generuje kód pro danou funkci. Hlavičkový soubor <intrin.h> obsahuje všechny dostupné vnitřní objekty pro každou z podporovaných hardwarových platforem.

Vnitřní objekty umožňují programátorovi přejít hluboko do kódu, aniž by musel používat sestavení. Použití vnitřních objektů má několik výhod:

  • Váš kód je přenosnější. Několik vnitřních objektů je k dispozici v několika architekturách procesoru.

  • Váš kód je čitelnější, protože kód je stále napsaný v jazyce C/C++.

  • Váš kód získá výhodu optimalizace kompilátoru. Jak se kompilátor zlepší, generování kódu pro vnitřní objekty se zlepší.

Další informace naleznete v tématu Vnitřní funkce kompilátoru.

Výjimky

K dosažení výkonu dochází při použití výjimek. Některá omezení se zavádějí při použití bloků try, které brání kompilátoru v provádění určitých optimalizací. Na platformách x86 dochází k dalšímu snížení výkonu z bloků try kvůli dalším informacím o stavu, které je potřeba vygenerovat během provádění kódu. Na 64bitových platformách zkuste bloky snížit výkon tolik, ale jakmile dojde k výjimce, proces nalezení obslužné rutiny a odvíjení zásobníku může být nákladný.

Proto se doporučuje vyhnout se zavedení bloků try/catch do kódu, které ho skutečně nepotřebují. Pokud je nutné použít výjimky, použijte pokud je to možné, synchronní výjimky. Další informace najdete v tématu Strukturované zpracování výjimek (C/C++).

Nakonec vyvolají výjimky pouze pro výjimečné případy. Použití výjimek pro obecný tok řízení pravděpodobně způsobí snížení výkonu.

Viz také