Freigeben über


Problembehandlung der Auswirkungen der Inlinefunktionen zur Buildzeit

Verwenden der Build Insights-Ansicht Funktionen zur Problembehandlung der Auswirkungen von Funktions-Inlining auf die Build-Zeit in Ihren C++-Projekten.

Voraussetzungen

  • Visual Studio 2022 17.8 oder höher.
  • C++ Build Insights ist standardmäßig aktiviert, wenn Sie entweder die Desktopentwicklung mit C++-Workload oder die Spieleentwicklung mit C++-Workload installieren.

Screenshot des Visual Studio-Installers mit ausgewählter C++-Workload für die Desktopentwicklung.

Die Liste der installierten Komponenten wird angezeigt. C++ Build Insights ist hervorgehoben und ausgewählt, was bedeutet, dass es installiert ist.

Screenshot des Visual Studio-Installers mit ausgewählter C++-Workload für die Spieleentwicklung.

Die Liste der installierten Komponenten wird angezeigt. C++ Build Insights ist hervorgehoben und ausgewählt, was bedeutet, dass es installiert ist.

Übersicht

Build Insights, jetzt in Visual Studio integriert und hilft Ihnen, Ihre Buildzeiten zu optimieren – insbesondere für große Projekte wie AAA-Spiele. Build Insights bietet Analysen, wie z. B. die Ansicht Funktionen, die bei der Diagnose der teuren Codegenerierung während der Build-Zeit hilft. Sie zeigt die Zeit an, die für die Generierung von Code für jede Funktion benötigt wird, und zeigt die Auswirkungen von __forceinline.

Die Anweisung __forceinline weist den Compiler an, eine Funktion unabhängig von ihrer Größe oder Komplexität inline zu verwenden. Das Inlining einer Funktion kann die Laufzeitleistung verbessern, indem es den Overhead beim Aufruf der Funktion reduziert. Der Nachteil dabei ist, dass die Größe der Binärdatei zunehmen und die Buildzeit beeinträchtigt werden kann.

Bei optimierten Builds trägt die Zeit, die für die Codegenerierung aufgewendet wird, erheblich zur gesamten Buildzeit bei. Im Allgemeinen erfolgt die Optimierung der C++-Funktion schnell. In Ausnahmefällen können einige Funktionen so groß und komplex werden, dass sie den Optimierer unter Druck setzen und Ihre Builds merklich verlangsamen.

In diesem Artikel erfahren Sie, wie Sie die Ansicht „Build Insights“-Funktionen verwenden, um Inlining-Engpässe in Ihrem Build zu finden.

Festlegen von Buildoptionen

Um die Ergebnisse von __forceinline zu messen, verwenden Sie einen Release-Build, denn Debug-Builds verwenden __forceinline nicht inline, da Debug-Builds den Compilerswitch /Ob0 verwenden, der diese Optimierung deaktiviert. Legen Sie den Build für Release und x64 fest:

  1. Wählen Sie im Dropdownmenü Projektmappenkonfigurationen die Option Release aus.
  2. Wählen Sie im Dropdownmenü Projektmappenplattformen die Option x64 aus.

Screenshot der Dropdownliste „Projektmappenkonfiguration“ mit der Einstellung „Release“ und der Dropdownliste „Projektmappenplattform“ mit der Einstellung „x64“.

Legen Sie die Optimierungsstufe auf maximale Optimierungen fest:

  1. Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf den Projektnamen, und wählen Sie Eigenschaften aus.

  2. Navigieren Sie in den Projekteigenschaften zu C/C++>Optimierung.

  3. Legen Sie die Dropdownliste Optimierung auf Maximale Optimierung (Geschwindigkeit bevorzugen) (/O2) fest.

    Screenshot des Dialogfelds „Projekteigenschaftsseiten“. Die Einstellungen sind unter Konfigurationseigenschaften >C/C++>-Optimierung geöffnet. Die Dropdownliste „Optimierung“ ist auf „Maximale Optimierung (Geschwindigkeit bevorzugen) (/O2)“ festgelegt.

  4. Klicken Sie auf OK, um das Dialogfeld zu schließen.

Ausführen von Build Insights

Führen Sie in einem Projekt Ihrer Wahl und mit den im vorherigen Abschnitt festgelegten Release-Buildoptionen Build Insights aus, indem Sie im Hauptmenü Erstellen>Build Insights für Auswahl ausführen>Neu erstellen auswählen. Sie können auch mit der rechten Maustaste auf ein Projekt im Projektmappen-Explorer klicken und Build Insights ausführen>Neu erstellen auswählen. Wählen Sie "Neu erstellen" aus, anstatt die Buildzeit für das gesamte Projekt zu messen , und nicht nur für die wenigen Dateien, die zurzeit schmutzig sind.

Screenshot des Hauptmenüs mit ausgewählter Option „Build Insights für Auswahl ausführen“ > „Neu erstellen“.

Nach Abschluss des Builds wird eine ETL-Datei (Event Trace Log) geöffnet. Sie wird im Ordner gespeichert, auf den die Windows-Umgebungsvariable TEMP verweist. Der generierte Name basiert auf der Sammlungszeit.

Funktionsansicht

Wählen Sie im Fenster für die ETL-Datei die Registerkarte Funktionen. Dort finden Sie die Funktionen, die kompiliert wurden, und die Zeit, die für die Generierung des Codes für jede Funktion benötigt wurde. Wenn der für eine Funktion generierte Code vernachlässigbar ist, wird er nicht in der Liste angezeigt, um zu vermeiden, dass die Leistung der Build-Ereignissammlung beeinträchtigt wird.

Screenshot der Ansichtsdatei „Build Insights-Funktionen“.

In der Spalte „Function Name“ ist performPhysicsCalculations() hervorgehoben und mit einem Feuersymbol markiert.

In der Spalte Zeit [Sek., %] wird gezeigt, wie lange es dauerte, jede Funktion in Wall Clock Responsibility Time (WCTR) zu kompilieren. Diese Metrik verteilt die Wanduhrzeit auf die Funktionen auf der Grundlage ihrer Verwendung von parallelen Compiler-Threads. Wenn z. B. zwei verschiedene Threads gleichzeitig an zwei unterschiedlichen Funktionen innerhalb eines Zeitraums von einer Sekunde arbeiten, wird die WCTR jeder Funktion als 0,5 Sekunden erfasst. Dies spiegelt den proportionalen Anteil jeder Funktion an der Gesamtkompilierungszeit wider, wobei die Ressourcen berücksichtigt werden, die während der parallelen Ausführung verbraucht wurden. Daher ermöglicht die WCTR eine bessere Messung der Auswirkungen jeder Funktion auf die Gesamtbuildzeit in Umgebungen, in denen mehrere Kompilierungsaktivitäten gleichzeitig erfolgen.

Die Spalte Forceinline Size zeigt, wie viele Anweisungen für die Funktion ungefähr generiert wurden. Klicken Sie auf das Chevron vor dem Funktionsnamen, um die einzelnen inlineierten Funktionen anzuzeigen, die in dieser Funktion erweitert wurden, und ungefähr die Anzahl der Anweisungen, die jeweils generiert wurden.

Sie können die Liste sortieren, indem Sie die Spalte Time auswählen, um zu sehen, welche Funktionen die meiste Zeit zum Kompilieren benötigen. Ein "Feuer"-Symbol zeigt an, dass die Kosten für die Generierung dieser Funktion hoch sind und es wert ist, zu untersuchen. Übermäßige Verwendung von __forceinline-Funktionen kann die Kompilierung erheblich verlangsamen.

Sie können mithilfe des Felds Filterfunktionen nach einer bestimmten Funktion suchen. Wenn die Codegenerierungszeit einer Funktion zu gering ist, erscheint sie nicht in der Ansicht Funktionen.

Verbessern der Buildzeit durch Anpassen des Funktionsinlining

In diesem Beispiel benötigt die performPhysicsCalculations-Funktion die meiste Zeit zum Kompilieren.

Screenshot der Ansicht „Build Insights-Funktionen“.

In der Spalte „Function Name“ ist performPhysicsCalculations() hervorgehoben und mit einem Feuersymbol markiert.

Durch Auswählen des Chevrons vor dieser Funktion und anschließendes Sortieren der Spalte " Forceinline Size " vom höchsten zum niedrigsten, sehen wir die größten Mitwirkenden für das Problem.

Screenshot der Ansichtsdatei „Build Insights-Funktionen“ mit einer ausgeklappten Funktion.

performPhysicsCalculations() ist ausgeklappt und zeigt eine lange Liste von Funktionen an, die Inline eingefügt wurden. Es werden mehrere Instanzen von Funktionen wie complexOperation(), recursiveHelper() und sin() angezeigt. Die Spalte „Forceinline Size“ zeigt, dass complexOperation() mit 315 Anweisungen die größte Inline-Funktion ist. recursiveHelper() hat 119 Anweisungen. Sin() hat 75 Anweisungen, aber es gibt viel mehr Instanzen dieser Funktion als bei den anderen Funktionen.

Es gibt einige größere Inline-Funktionen, wie z. B. Vector2D<float>::complexOperation() und Vector2D<float>::recursiveHelper(), die zu diesem Problem beitragen. Aber es gibt viele weitere Instanzen (nicht alle hier gezeigt) von Vector2D<float>::sin(float), Vector2D<float>::cos(float), Vector2D<float>::power(float,int) und Vector2D<float>::factorial(int). Wenn Sie diese zusammenzählen, übersteigt die Gesamtzahl der generierten Anweisungen schnell die wenigen größeren generierten Funktionen.

Wenn wir uns diese Funktionen im Quellcode ansehen, sehen wir, dass die Ausführungszeit in Schleifen verbracht wird. Dies ist beispielsweise der Code für factorial():

static __forceinline T factorial(int n)
{
    T result = 1;
    for (int i = 1; i <= n; ++i) {
        for (int j = 0; j < i; ++j) {
            result *= (i - j) / (T)(j + 1);
        }
    }
    return result;
}

Vielleicht sind die Gesamtkosten für den Aufruf dieser Funktion unbedeutend im Vergleich zu den Kosten der Funktion selbst. Eine Funktion inline zu verwenden ist dann am vorteilhaftesten, wenn die Zeit, die für den Aufruf der Funktion benötigt wird (Argumente auf den Stack legen, zur Funktion springen, Rückgabeargumente einfügen und aus der Funktion zurückkehren), ungefähr der Zeit entspricht, die für die Ausführung der Funktion benötigt wird, und wenn die Funktion häufig aufgerufen wird. Wenn das nicht der Fall ist, kann der Nutzen einer Inline- Anwendung abnehmen. Wir können versuchen, die Anweisung __forceinline zu entfernen, um zu sehen, ob dies die Erstellungszeit verkürzt. Der Code für power, sin()und cos() ist ähnlich darin, dass der Code aus einer Schleife besteht, die viele Male ausgeführt wird. Wir können auch versuchen, die __forceinline-Anweisung aus diesen Funktionen zu entfernen.

Wir führen Build Insights über das Hauptmenü erneut aus, indem wir Erstellen>Build Insights für Auswahl ausführen>Neu erstellen auswählen. Sie können auch mit der rechten Maustaste auf ein Projekt im Projektmappen-Explorer klicken und Build Insights ausführen>Neu erstellen auswählen. Wir wählen "Neu erstellen" anstelle von "Build " aus, um die Buildzeit für das gesamte Projekt wie zuvor zu messen und nicht nur für die wenigen Dateien, die zurzeit schmutzig sind.

Die Buildzeit sinkt von 25,181 Sekunden auf 13,376 Sekunden und die Funktion performPhysicsCalculations wird in der Ansicht Funktionen nicht mehr angezeigt, da sie nicht genug zur Buildzeit beiträgt, um gezählt zu werden.

Screenshot der 2D-Vektorheader-Datei

In der Spalte „Function Name“ ist performPhysicsCalculations() hervorgehoben und mit einem Feuersymbol markiert.

Die Diagnosesitzungszeit ist die Gesamtzeit, die zum Erstellen des Builds benötigt wurde, sowie jeglicher Aufwand zum Sammeln der Build Insights-Daten.

Der nächste Schritt wäre, ein Profil der Anwendung zu erstellen, um festzustellen, ob die Leistung der Anwendung durch die Änderung beeinträchtigt wird. Wenn dies der Fall ist, können wir __forceinline nach Bedarf selektiv wieder hinzufügen.

Doppelklicken Sie, klicken Sie mit der rechten Maustaste, oder drücken Sie auf einer Datei die Eingabetaste, um in der Ansicht Funktionen den Quellcode für diese Datei zu öffnen.

Screenshot eines Rechtsklicks auf eine Datei in der Ansicht „Funktionen“. Die Menüoption „Gehe zur Quelldatei“ ist hervorgehoben.

Tipps

  • Verwenden Sie Datei>Speichern unter, um die ETL-Datei an einem dauerhaften Speicherort zu speichern und somit die Informationen zur Buildzeit aufzubewahren. Sie können sie dann mit zukünftigen Builds vergleichen, um zu sehen, wie Ihre Änderungen die Dinge verbessern.
  • Wenn Sie das Fenster "Build Insights" schließen, öffnen Sie es erneut, indem Sie die <dateandtime>.etl Datei in Ihrem temporären Ordner suchen. Die Windows-Umgebungsvariable TEMP gibt den Pfad des Ordners für temporäre Dateien an.
  • Um die Build Insights-Daten mit Windows Performance Analyzer (WPA) zu analysieren, wählen Sie die Schaltfläche In WPA öffnen unten rechts im ETL-Fenster aus.
  • Ziehen Sie Spalten, um deren Reihenfolge zu ändern. Sie können z. B. die Spalte Time zur ersten Spalte machen. Sie können Spalten ausblenden, indem Sie mit der rechten Maustaste auf die Spaltenüberschrift klicken und die Spalten abwählen, die Sie nicht benötigen.
  • Die Ansicht Funktionen bietet ein Filterfeld, mit dem Sie eine Funktion finden können, die Sie interessiert. Es erfolgt ein Teilabgleich für den eingegebenen Namen.
  • Wenn Sie nicht mehr wissen, wie Sie die Ansicht Funktionen interpretieren sollen, bewegen Sie den Mauszeiger über die Registerkarte, um eine QuickInfo mit einer Beschreibung der Ansicht anzuzeigen. Wenn Sie den Mauszeiger über die Registerkarte Funktionen bewegen, erscheint in der QuickInfo: „Ansicht, die Statistiken für Funktionen anzeigt, bei denen die untergeordneten Knoten Force-Inline-Funktionen sind.“

Problembehandlung

  • Wenn das Fenster „Build Insights“ nicht angezeigt wird, führen Sie einen Rebuild anstelle eines Builds durch. Das Fenster „Build Insights“ wird nicht angezeigt, wenn kein tatsächlicher Build erfolgt. Das kann der Fall sein, wenn sich keine Dateien seit dem letzten Build geändert haben.
  • Wenn in der Ansicht „Funktionen“ keine Funktionen angezeigt werden, haben Sie möglicherweise nicht die richtigen Optimierungseinstellungen gewählt. Vergewissern Sie sich, dass Sie Release mit allen Optimierungen erstellen, wie in Build-Optionen festlegen beschrieben. Wenn die Codegenerierungszeit einer Funktion zu gering ist, erscheint sie nicht in der Liste.

Weitere Informationen

Tipps und Tricks zu Build Insights
Inlinefunktionen (C++)
Schnellere C++-Builds vereinfacht: eine neue Metrik für die Zeit
Video: Build Insights in Visual Studio – Pure Virtual C++ 2023
Problembehandlung bei Auswirkungen von Headerdateien auf die Buildzeit
Ansicht „Funktionen“ für Build Insights in Visual Studio 2022 17.8
Tutorial: vcperf und Windows Performance Analyzer
Verbessern der Codegenerierungszeit mit C++ Build Insights