Tutorial: Problembehandlung bei der Funktions-Inlining zur Erstellungszeit

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 Workload „Desktopentwicklung mit C++“ oder die Workload „Spieleentwicklung mit C++“ 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 anstelle von Erstellen aus, um die Buildzeit für das gesamte Projekt zu messen und nicht nur für die wenigen geänderten Dateien.

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 die Menge an Code, die für eine Funktion generiert wird, vernachlässigbar ist, erscheint sie nicht in der Liste, um die Leistung der Build-Ereignissammlung nicht zu beeinträchtigen.

Screenshot der Ansichtsdatei „Build Insights-Funktionen“.

In der Spalte Funktionsname 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 zum Beispiel zwei verschiedene Threads innerhalb einer Sekunde zwei verschiedene Funktionen gleichzeitig kompilieren, wird die WCTR jeder Funktion mit 0,5 Sekunden angegeben. Diese spiegelt den proportionalen Anteil der einzelnen Funktionen an der Gesamtkompilierungszeit wider, wobei die Ressourcen berücksichtigt werden, die während der parallelen Ausführung verbraucht werden. 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. Wählen Sie die Raute vor dem Funktionsnamen, um die einzelnen Inline-Funktionen zu sehen, die in dieser Funktion erweitert wurden, und um zu sehen, wie viele Anweisungen für jede davon 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 Erstellung dieser Funktion hoch sind und es sich lohnt, sie 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.

Wenn Sie die Raute vor dieser Funktion markieren und dann die Spalte Forceinline Size vom höchsten zum niedrigsten Wert sortieren, sehen Sie, wer am meisten zu dem Problem beiträgt.

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 insofern ähnlich, als der Code aus einer Schleife besteht, die mehrmals ausgeführt wird. Wir können auch versuchen, die __forceinline-Anweisung aus diesen Funktionen zu entfernen.

Wir führen Build Insights aus dem Hauptmenü erneut aus, indem wir Build>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 Erstellen aus, um wie zuvor die Buildzeit für das gesamte Projekt zu messen und nicht nur für die wenigen geänderten Dateien.

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 Funktionsname ist performPhysicsCalculations() hervorgehoben und mit einem Feuersymbol markiert.:::

Die Zeit für die Diagnosesitzung ist die Gesamtzeit, die für den Build benötigt wurde, zuzüglich des Aufwands für die Erfassung 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

  • Sie können Datei>Speichern unter auswählen, um die ETL-Datei an einem beständigeren Speicherort speichern, damit die Buildzeit festgehalten wird. Sie können sie dann mit zukünftigen Builds vergleichen, um festzustellen, ob Ihre Änderungen die Buildzeit verbessern.
  • Wenn Sie das Fenster „Build Insights“ versehentlich 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

Inlinefunktionen (C++)
Faster C++ builds, simplified: a new metric for time (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-Erkenntnisse in Visual Studio 2022 17.8
Tutorial: vcperf und Windows Performance Analyzer
Improving code generation time with C++ Build Insights (Verbessern der Codegenerierungszeit mit C++ Build Insights)