Empfohlene Vorgehensweisen für die Optimierung
Aktualisiert: November 2007
In diesem Dokument werden einige bewährte Methoden für die Optimierung in Visual C++ 2005 beschrieben. Die folgenden Themen werden erörtert:
Optionen für Compiler und Linker
Änderungen in Visual C++ 2005
Welche Optimierungsstufe sollte verwendet werden?
Gleitkommaschalter
Optimierung mit declspecs
Optimierung mit Pragmas
__restrict und __assume
Unterstützung für systeminterne Funktionen
Ausnahmen
Optionen für Compiler und Linker
Änderungen in Visual C++ 2005
Visual C++ 2005 unterstützt jetzt die profilgesteuerte Optimierung (PGO). Diese Optimierung verwendet Profildaten aus früheren Ausführungen einer instrumentierten Version einer Anwendung, um eine spätere Optimierung der Anwendung zu erreichen. Das Verwenden der profilgesteuerte Optimierung (PGO) kann zeitaufwendig sein, sodass sie möglicherweise nicht von allen Entwicklern genutzt wird. Wir empfehlen jedoch, die profilgesteuerte Optimierung (PGO) für das endgültige Releasebuild eines Projekts zu verwenden. Weitere Informationen finden Sie unter Profilgesteuerte Optimierungen (PGO).
Außerdem wurden die Komplette Programmoptimierung (auch Link-Zeitcodegenerierung genannt) sowie die /O1-Optimierung und die /O2-Optimierung verbessert. Allgemein gilt, daß eine Anwendung, die mit einer dieser Optionen kompiliert wurde, sich durch eine höhere Geschwindigkeit auszeichnet als dieselbe Anwendung, die mit einem früheren Compiler kompiliert wurde.
Weitere Informationen finden Sie unter /GL (Optimierung des ganzen Programms) und /O1, /O2 (Größe minimieren, Geschwindigkeit maximieren).
Welche Optimierungsstufe sollte verwendet werden?
Wenn es möglich ist, sollten endgültige Releasebuilds auf jeden Fall unter Verwendung der profilgesteuerten Optimierung (PGO) kompiliert werden. Falls eine profilgesteuerte Optimierung (PGO) nicht möglich sein sollte, weil z. B. die Infrastruktur zur Ausführung der instrumentierten Builds nicht ausreicht oder kein Zugang zu Szenarios besteht, wird das Ausführen des Buildvorgangs mit der Kompletten Programmoptimierung empfohlen.
Der /Gy-Schalter ist auch sehr hilfreich. Er generiert ein separates COMDAT für jede Funktion und verleiht dem Linker dadurch mehr Flexibilität beim Entfernen COMDATs, auf die nicht verwiesen wird, und bei der COMDAT-Faltung. Der einzige Nachteil bei der Verwendung von /Gy besteht in einer u. U. geringfügigen Verlängerung der Buildzeit. Diese Option sollte also generell verwendet werden. Weitere Informationen finden Sie unter /Gy (Funktionslevel-Linking aktivieren).
Beim Linken in 64-Bit-Umgebungen wird die Verwendung der /OPT:REF,ICF-Linkeroption empfohlen, und in 32-Bit-Umgebungen wird /OPT:REF empfohlen. Weitere Informationen finden Sie unter /OPT (Optimierungen).
Es wird dringend empfohlen, Debugsymbole zu generieren. Dies gilt sogar für optimierte Releasebuilds. Die Symbole beeinflussen den generierten Code nicht, erleichtern jedoch das ggf. nötige Debuggen der Anwendung sehr.
Gleitkommaschalter
In Visual C++ 2005 wurde die /Op-Compileroption entfernt. Zugleich wurden die vier folgenden Compileroptionen für Gleitkommaoptimierungen hinzugefügt:
/fp:precise |
Dies ist die als Standard empfohlene Option, die in den meisten Fällen verwendet werden sollte. |
/fp:fast |
Empfohlen für die Fälle, in denen die Leistung von höchster Wichtigkeit ist, z. B. bei Spielen. Dies führt zur schnellsten Programmausführung. |
/fp:strict |
Empfohlen für die Fälle, in denen präzise Gleitkommaausnahmen und IEEE-Verhalten gewünscht werden. Dies führt zur langsamsten Programmausführung. |
/fp:except[-] |
Kann in Verbindung mit /fp:strict oder /fp:precise verwendet werden, aber nicht mit /fp:fast. |
Weitere Informationen finden Sie unter /fp (Festlegen des Gleitkommaverhaltens).
Optimierung mit declspecs
In diesem Abschnitt werden zwei declspecs erläutert, die zur Leistungssteigerung in Programmen verwendet werden können: __declspec(restrict) und __declspec(noalias).
Die restrict-declspec kann nur auf Funktionsdeklarationen angewendet werden, die einen Zeiger zurückgeben, z. B. __declspec(restrict) void *malloc(size_t size);
Die restrict-declspec wird bei Funktionen verwendet, die Zeiger ohne Alias zurückgeben. Dieses Schlüsselwort wird für die C-Laufzeitbibliotheksimplementierung von malloc verwendet, da es niemals einen im aktuellen Programm bereits verwendeten Zeigerwert zurückgibt (außer bei unzulässigen Vorgängen, z. B. beim Verwenden von Speicher, nachdem dieser freigegeben wurde).
Die restrict-declspec liefert dem Compiler weitere Informationen zur Durchführung von Compileroptimierungen. Eine der schwierigsten Aufgaben des Compilers ist es, zu ermitteln, welche Zeiger als Alias für andere Zeiger dienen. Die Verwendung solcher Informationen ist für den Compiler ausgesprochen hilfreich.
Es muss aber erwähnt werden, dass diese Informationen vom Compiler als Zusicherung behandelt werden, d. h., sie werden vom Compiler nicht überprüft. Wenn das Programm diese restrict-declspec unpassend verwendet, führt dies u. U. zu einem fehlerhaften Verhalten des Programms.
Weitere Informationen finden Sie unter restrict.
Die noalias-declspec wird ebenfalls nur auf Funktionen angewendet, und sie gibt an, dass die Funktion eine halbreine Funktion ist. Eine halbreine Funktion ist eine Funktion, die nur lokale Variablen, Argumente und Dereferenzierungen der ersten Ebene von Argumenten ändert oder auf diese verweist. Diese declspec ist eine Zusicherung für den Compiler. Wenn die Funktion auf Globals oder auf Dereferenzierungen der zweiten Ebene von Zeigerargumenten verweist, generiert der Compiler u. U. Code, zum Abbruch der Anwendung führt.
Weitere Informationen finden Sie unter noalias.
Optimierung mit Pragmas
Code kann auch mithilfe einer Reihe von nützlichen Pragmas optimiert werden. Zuerst wird #pragma optimize erläutert:
#pragma optimize("{opt-list}", on | off)
Mithilfe dieses Pragmas können Sie für jede einzelne Funktion eine bestimmte Optimierungsstufe festlegen. Diese Möglichkeit eignet sich hervorragend für jene seltenen Fälle, in denen die Anwendung abstürzt, sobald eine Funktion mithilfe der Optimierung kompiliert wurde. Mit diesem Pragma können Sie die Optimierungen für eine einzelne Funktion deaktivieren:
#pragma optimize("", off)
int foo() {...}
#pragma optimize("", on)
Weitere Informationen finden Sie unter optimize.
Das Inlining stellt eine der wichtigsten Optimierungen dar, die der Compiler durchführt. Daher sollen an dieser Stelle einige Pragmas erläutert werden, mit der dieses Verhalten geändert werden kann.
Mit #pragma inline_recursion kann angegeben werden, ob die Anwendung bei einem rekursiven Aufruf inlinefähig sein soll. Diese Option ist standardmäßig deaktiviert. Für die flache Rekursion bei kleinen Funktionen können Sie diese Option aktivieren. Weitere Informationen finden Sie unter inline_recursion.
Außerdem steht mit #pragma inline_depth ein nützliches Pragma zur Beschränkung der Tiefe beim Inlining zur Verfügung. Dieses Pragma kann i. d. R. verwendet werden, wenn Sie die Größe des Programms oder einer Funktion beschränken möchten. Weitere Informationen finden Sie unter inline_depth.
__restrict und __assume
In Visual C++ 2005 sind einige Schlüsselwörter enthalten, die zum Verbessern der Leistung beitragen. Hierzu zählen z. B. __restrict und __assume.
Zuerst muss darauf hingewiesen werden, dass es sich bei __restrict und __declspec(restrict) um zwei verschiedene Elemente handelt. Sie ähneln zwar einander, doch unterscheiden sie sich in ihrer Semantik. __restrict ist ein Typbezeichner wie const oder volatile, dient jedoch ausschließlich für Zeigertypen.
Ein Zeiger, der durch __restrict modifiziert wird, wird als __restrict-Zeiger bezeichnet. Ein __restrict-Zeiger ist ein Zeiger, auf den nur über den __restrict-Zeiger zugegriffen werden kann. Das heißt, mit einem anderen Zeiger kann nicht auf die Daten zugegriffen werden, auf die der __restrict-Zeiger zeigt.
__restrict kann ein leistungsfähiges Tool für den Visual C++-Optimierer sein, doch sollten Sie es mit großer Umsicht verwenden. Wenn Sie dieses Tool unangemessen verwenden, führt der Optimierer u. U. eine Optimierung durch, die zum Abbruch der Anwendung führt.
Das __restrict-Schlüsselwort in Visual C++ 2005 ersetzt den /Oa-Schalter aus den vorherigen Versionen.
__assume ist bereits seit mehreren Versionen in Visual C++ enthalten, aber in Visual C++ 2005 hat es einen viel höheren Nutzwert. Mit __assume, kann ein Entwickler den Compiler anweisen, Annahmen über den Wert einer Variablen zu treffen.
Beispielsweise teilt __assume(a < 5); dem Compiler mit, dass in dieser Zeile des Codes die Variable a kleiner als 5 ist. Hierbei handelt es sich wiederum um eine Zusicherung an den Compiler. Wenn a an dieser Stelle des Programms in Wirklichkeit den Wert 6 hat, entspricht das Verhalten des Programms nach der Optimierung durch den Compiler möglicherweise nicht Ihren Erwartungen. __assume wird am sinnvollsten vor switch-Anweisungen und/oder bedingten Ausdrücken verwendet.
Für __assume gelten einige Beschränkungen. Es handelt sich hierbei genau wie bei __restrict nur um einen Vorschlag, der vom Compiler ggf. ignoriert werden kann. Zum anderen funktioniert __assume derzeit nur mit Ungleichungen aus Variablen und Konstanten. Ungleichungen mit Symbolen werden nicht weitergegeben, z. B. __assume(a < b).
Unterstützung für systeminterne Funktionen
Systeminterne Funktionen werden bei Funktionsaufrufen verwendet, bei denen der Compiler auf ein systeminternes Wissen über den Aufruf zurückgreifen kann und keine Funktion in einer Bibliothek aufzurufen braucht. Stattdessen wird der Code für diese Funktion vom Compiler ausgegeben. Die Unterstützung für systeminterne Funktionen wurde in Visual C++ 2005 stark verbessert. Die Headerdatei intrin.h befindet sich in <Installationsverzeichnis>\VC\include\intrin.h und enthält alle verfügbaren systeminternen Funktionen für alle drei unterstützten Plattformen (x86, x64 und Itanium).
Dank systeminterner Funktionen können Programmierer tief in den Code gehen, ohne Assemblys zu verwenden. Die Verwendung systeminterner Funktionen hat mehrere Vorteile:
Der Code ist besser portabel. Eine Reihe von systeminternen Funktionen sind auf verschiedenen CPU-Architekturen verfügbar.
Der Code ist leichter lesbar, da er weiterhin in C/C++ geschrieben wird.
Die Vorteile von Compileroptimierungen können für den Code genutzt werden. Mit der zunehmenden Verbesserung des Compilers verbessert sich auch die Codegenerierung für die systeminternen Funktionen.
Weitere Informationen finden Sie unter Compiler Intrinsics und Benefits of Using Intrinsics.
Ausnahmen
Die Verwendung von Ausnahmen ist mit Leistungseinbußen verbunden. Wenn Sie try-Blöcke verwenden, die das Ausführen bestimmter Optimierungen durch den Compiler verhindern, führt dies zu einer Reihe von Einschränkungen. Auf x86-Plattformen treten durch try-Blöcke weitere Leistungsminderungen auf, denn während der Codeausführung müssen zusätzliche Zustandsinformationen generiert werden. Auf den 64-Bit-Plattformen verschlechtern try-Blöcke die Leistung nicht so stark. Wenn jedoch eine Ausnahme ausgelöst wird, kann der Prozess für die Suche nach dem Handler und das Entladen des Stapels sehr aufwendig sein.
Deshalb wird empfohlen, try/catch-Blöcke so weit wie möglich zu vermeiden und sie nur dann in den Code einzufügen, wenn dies wirklich unabdingbar ist. Wenn Sie Ausnahmen verwenden müssen, sollten Sie wenn möglich auf synchrone Ausnahmen zurückgreifen. Weitere Informationen finden Sie unter Structured Exception Handling (C++).
Und nicht zu vergessen: Lösen Sie Ausnahmen nur für Ausnahmefälle aus. Das Verwenden von Ausnahmen für die allgemeine Ablaufsteuerung beeinträchtigt i. d. R. die Leistung.