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ář __assume
můž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.