Tutorial: Problembehandlung bei Auswirkungen von Headerdateien auf die Buildzeit

Verwenden Sie die Build Insights-Ansichten Eingeschlossene Dateien und Includestruktur, um die Auswirkungen von #include-Dateien auf C- und C++-Buildzeiten zu beheben.

Voraussetzungen

  • Visual Studio 2022 17.8 oder höher.
  • C++ Build Insights ist standardmäßig aktiviert, wenn Sie die Workload „Desktopentwicklung mit C++“ mit dem Visual Studio-Installer 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.

Oder die Workload „Spieleentwicklung mit C++“:

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 Triple-A-Spiele. Wenn eine große Headerdatei geparst wird, insbesondere dann, wenn es wiederholt geschieht, wirkt sich dies auf die Buildzeit aus.

Build Insights bietet Analysen in der Ansicht Eingeschlossenen Dateien, die helfen, die Auswirkungen des Parsens von #include-Dateien in Ihrem Projekt zu diagnostizieren. Sie zeigt die Zeit an, die benötigt wird, um jede Headerdatei zu parsen, und eine Ansicht der Beziehungen zwischen Headerdateien.

In diesem Artikel erfahren Sie, wie Sie die Build Insights-Ansichten Eingeschlossene Dateien und Includestruktur verwenden, um die am zeitaufwändigsten zu parsenden Headerdateien zu identifizieren und die Buildzeit zu optimieren, indem Sie eine vorkompilierte Headerdatei erstellen.

Festlegen von Buildoptionen

Legen Sie vor dem Sammeln von Build Insights-Daten die Buildoptionen für den Buildtyp fest, den Sie messen möchten. Wenn Sie beispielsweise Ihre x64-Debugbuildzeit herausfinden möchten, legen Sie den Build für Debug- und x64 fest:

  • Wählen Sie im Dropdownmenü Projektmappenkonfigurationen die Option Debuggen aus.

  • Wählen Sie im Dropdownmenü Projektmappenplattformen die Option x64 aus.

    Screenshot der Dropdownmenüs „Projektmappenkonfigurationen“.

    Das Dropdownmenü „Projektmappenkonfigurationen“ wird angezeigt. Es verfügt über die Optionen „Debug“, „Release“ und „Configuration Manager“. Das Dropdownmenü „Projektmappenplattform“ ist auf „x64“ festgelegt.

Ausführen von Build Insights

Führen Sie in einem Projekt Ihrer Wahl und mit den im vorherigen Abschnitt festgelegten Debug-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.

Ansicht „Eingeschlossene Dateien“

Die Ablaufverfolgungsdatei zeigt die Buildzeit an, die für dieses Beispiel 16,404 Sekunden betrug. Die Diagnosesitzung ist die Gesamtzeit für die Ausführung der Build Insights-Sitzung. Wählen Sie die Registerkarte Eingeschlossene Dateien aus.

In dieser Ansicht wird die Verarbeitungsdauer für #include-Dateien angezeigt.

Screenshot der Ansicht „Eingeschlossene Dateien“.

In der Spalte „Dateipfad“ werden mehrere Dateien mit einem Feuersymbol hervorgehoben, da sie mehr als 10 % der Buildzeit fürs Parsen einnehmen. „winrtHeaders.h“ ist die größte bei 8,581 Sekunden oder 52,3 % der Buildzeit von 16,404 Sekunden.

In der Spalte Dateipfad ist neben einigen Dateien ein Feuersymbol, das angibt, dass sie 10 % oder mehr der Buildzeit einnehmen.

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 Wall Clock Time, die benötigt wird, um Dateien basierend auf ihrer Verwendung paralleler Threads zu parsen. Wenn z. B. zwei verschiedene Threads zwei verschiedene Dateien innerhalb eines einsekündigen Zeitraums gleichzeitig parsen, wird die WCTR jeder Datei als 0,5 Sekunden aufgezeichnet. Diese spiegelt den proportionalen Anteil der einzelnen Dateien 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 Datei auf die Gesamtbuildzeit in Umgebungen, in denen mehrere Kompilierungsaktivitäten gleichzeitig erfolgen.

Die spalte Parseanzahl zeigt an, wie oft die Headerdatei geparst wurde.

Die erste in dieser Liste hervorgehobene Headerdatei ist winrtHeaders.h. Sie nimmt 8,581 Sekunden der gesamten Buildzeit von 16,404 Sekunden oder 52,3 % der Buildzeit ein. Die nächste ist Windows.UI.Xaml.Interop.h und dann Windows.Xaml.h.

Wenn Sie sehen möchten, welche Datei winrtHeaders.h enthält, klicken Sie auf das Chevron daneben. Die Spalte Parseanzahl kann hilfreich sein, da sie angibt, wie oft eine Headerdatei von anderen Dateien eingeschlossen wird. Vielleicht ist eine Headerdatei mehrmals eingeschlossen, was ein Zeichen dafür sein könnte, dass es sich um einen guten Kandidaten für eine vorkompilierte Headerdatei oder ein Refactoring handelt.

Die Spalte Übersetzungseinheit zeigt an, welche Datei beim Verarbeiten der eingeschlossenen Datei verarbeitet wurde. In diesem Beispiel wurde winrtHeaders.h eingeschlossen, während Grapher.cpp kompiliert wurde:

Screenshot der Ansicht „Eingeschlossene Dateien“.

Eine ETL-Beispieldatei mit den eingeschlossenen Dateien für ein Beispielprojekt. In der Spalte „Dateipfad“ ist „winrtHeaders.h“ ausgewählt und erweitert. Die Erstellung dauert 8,219 Sekunden, was 50,1 % der Buildzeit ausmacht. Der untergeordnete Knoten ist „Grapher.cpp“, der auch als Übersetzungseinheit aufgeführt ist.

Die Spalte „Übersetzungseinheit“ kann dabei helfen, die Datei zu unterscheiden, die in Fällen kompiliert wurde, in denen eine Headerdatei mehrmals eingeschlossen ist, und Sie herausfinden möchten, wo dies am häufigsten geschieht.

Wir wissen, dass winrtHeaders.h zeitaufwändig zu parsen ist, aber wir können mehr erfahren.

Ansicht „Includestruktur“

In dieser Ansicht sind die untergeordneten Knoten die Dateien, die vom übergeordneten Knoten eingeschlossen werden. Dadurch können Sie die Beziehungen zwischen Headerdateien verstehen und Möglichkeiten finden, um zu reduzieren, wie oft eine Headerdatei analysiert wird.

Wählen Sie die Registerkarte Includestruktur in der ETL-Datei aus, um die Ansicht „Includestruktur“ anzuzeigen:

Screenshot der Ansicht „Includestruktur“.

Zeigt die Includestruktur für ein Projekt. In der Spalte „Dateipfad“ werden jede Datei, die andere Dateien einschließt, sowie die Anzahl der eingeschlossenen Dateien und die Parsedauer aufgeführt.

In dieser Ansicht werden in der Spalte Dateipfad alle Dateien angezeigt, die andere Dateien einschließen. Unter Includeanzahl ist zu sehen, wie viele Dateien diese Headerdatei enthält. Die Parsedauer dieser Datei wird aufgelistet, und wenn sie erweitert wird, sehen Sie die Parsedauer jeder einzelnen Headerdatei, die diese Headerdatei einschließt.

Zuvor haben wir festgestellt, dass das Parsen von winrtHeaders.h zeitaufwändig ist. Wenn wir winrtHeaders.h in das Textfeld Dateien filtern eingeben, können wir die Ansicht nur nach den Einträgen filtern, die winrtHeaders.h im Namen enthalten. Wenn Sie auf das Chevron neben winrtHeaders.h klicken, werden die eingeschlossenen Dateien angezeigt:

Screenshot der erweiterten Ansicht „Includestruktur“.

In der Spalte „Dateipfad“ werden die einzelnen Dateien aufgelistet, die andere Dateien einschließen, sowie die Anzahl der eingeschlossenen Dateien und die Parsedauer. „winrtHeaders.h“ ist ausgewählt und erweitert, um die darin eingeschlossenen Dateien anzuzeigen. „Windows.UI.Xaml.Interop.h“ ist eine dieser Dateien. Sie ist erweitert, um die darin eingeschlossenen Headerdateien anzuzeigen.

Wir sehen, dass winrtHeaders.h Windows.UI.Xaml.Interop.h enthält. Sie wissen von der Ansicht Eingeschlossene Dateien, dass diese auch zeitaufwändig zu parsen war. Klicken Sie auf das Chevron neben Windows.UI.Xaml.Interop.h, um zu sehen, dass Windows.UI.Xaml.h eingeschlossen ist und 21 weitere Headerdateien einschließt, von denen zwei ebenfalls ein Feuersymbol aufweisen.

Nachdem sie einige der am zeitaufwändigsten zu parsenden Headerdateien ermittelt und festgestellt haben, dass diese in winrtHeaders.h eingeschlossen sind, ist es empfehlenswert, einen vorkompilierten Header zu verwenden, um das Einschließen von winrtHeaders.h zu beschleunigen.

Verbessern der Buildzeit mit vorkompilierten Headern

Da wir aus der Ansicht Eingeschlossene Dateien wissen, dass winrtHeaders.h zeitaufwändig zu parsen ist, und aus der Ansicht Includestruktur, dass winrtHeaders.h mehrere andere Headerdateien einschließt, die zeitaufwendig zu parsen sind, erstellen wir eine vorkompilierte Headerdatei (PCH), um diesen Vorgang zu beschleunigen, indem alles nur noch einmal in einem PCH geparst wird.

Wir fügen pch.h hinzu, um winrtHeaders.h einzuschließen, was wie folgt aussehen würde:

#ifndef CALC_PCH
#define CALC_PCH

#include <winrtHeaders.h>

#endif // CALC_PCH

PCH-Dateien müssen kompiliert werden, bevor sie verwendet werden können. Daher fügen wir dem Projekt eine Datei hinzu, die willkürlich pch.cpp genannt wird und pch.h enthält. Sie enthält eine Zeile:

#include "pch.h"

Anschließend richten wir unser Projekt so ein, dass der PCH verwendet wird. Öffnen Sie die Projekteigenschaften über C/C++>Vorkompilierte Header, und legen Sie Vorkompilierter Header auf Verwenden (/Yu) und Vorkompilierte Headerdatei auf pch.h fest.

Screenshot des Dialogfelds „Projekteigenschaften“ mit geöffneten Einstellungen für vorkompilierte Header.

„Vorkompilierter Header“ ist auf „Verwenden (/Yu)“ festgelegt. Die vorkompilierte Headerdatei ist auf „pch.h“ festgelegt.

Um den PCH zu verwenden, fügen wir ihn als erste Zeile in die Quelldateien ein, die winrtHeaders.h verwenden. Er muss vor allen anderen eingeschlossenen Dateien stehen. Oder aus Gründen der Einfachheit könnten wir die Projekteigenschaften ändern, um pch.h am Anfang jeder Datei in die Projektmappe einzuschließen, indem die Projekteigenschaft C/C++>Erweitert>Erzwungene Includedateien auf pch.h festgelegt wird:

Screenshot des Dialogfelds „Projekteigenschaften“ mit geöffneten erweiterten Einstellungen.

„Erzwungene Includedateien“ ist auf „pch.h“ festgelegt.

Da der PCH winrtHeaders.h enthält, könnten wir winrtHeaders.h aus allen Dateien entfernen, die diesen derzeit einschließen. Es ist nicht unbedingt notwendig, da der Compiler erkennt, dass winrtHeaders.h bereits eingeschlossen ist und ihn nicht erneut parst. Einige Entwickler bevorzugen es, #include in der Quelldatei zu behalten – entweder zur Übersichtlichkeit oder für den Fall, dass für den PCH wahrscheinlich ein Refactoring erfolgt und er diese Headerdatei möglicherweise nicht mehr einschließt.

Testen der Änderungen

Wir bereinigen zunächst das Projekt, um sicherzustellen, dass wir den Buildvorgang für die gleichen Dateien wie zuvor vergleichen. Um nur ein Projekt zu bereinigen, klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf das Projekt, und wählen Sie Nur Projekt>Nur <Projektname> bereinigen aus.

Da dieses Projekt jetzt einen vorkompilierten Header (PCH) verwendet, möchten wir die Buildzeit des PCH nicht messen, da der Build nur einmal erfolgt. Dazu laden wir die pch.cpp-Datei und drücken STRG+F7, um nur diese Datei zu erstellen. Sie können diese Datei auch kompilieren, indem Sie im Projektmappen-Explorer mit der rechten Maustaste auf pch.cpp klicken und Compile auswählen.

Jetzt führen wir Build Insights im Projektmappen-Explorer erneut aus, indem wir mit der rechten Maustaste auf das Projekt klicken und Nur Projekt>Build Insights für Build ausführen auswählen. Sie können auch mit der rechten Maustaste auf ein Projekt im Projektmappen-Explorer klicken und Build Insights ausführen>Erstellen auswählen. Wir möchten dieses Mal nicht neu erstellen, da dadurch der PCH neu erstellt wird, den wir nicht messen möchten. Wir haben das Projekt zuvor bereinigt, was bedeutet, dass ein normaler Build alle Projektdateien kompiliert, die wir messen möchten.

Wenn die ETL-Dateien erscheinen, sehen wir, dass die Buildzeit von 16,404 Sekunden auf 6,615 Sekunden reduziert wurde. Geben Sie winrtHeaders.h in das Filterfeld ein. Nichts erscheint. Das liegt daran, dass die Parsedauer jetzt vernachlässigbar ist, da der Header vom vorkompilierten Header abgerufen wird.

Screenshot des Bereichs „Includestruktur“ in der Ablaufverfolgungsdatei. „winrtHeaders“ wird nicht mehr aufgeführt.

In diesem Beispiel werden vorkompilierte Header verwendet, da sie eine gängige Lösung vor C++20 sind. Ab C++20 gibt es jedoch schnellere und einfachere Möglichkeiten, Headerdateien einzuschließen, z. B. Headereinheiten und Module. Weitere Informationen finden Sie unter Vergleich von Headereinheiten, Modulen und vorkompilierten Headern.

Es gibt einige Navigationsfeatures für die Ansichten Eingeschlossene Dateien und Includestruktur:

  • Doppelklicken Sie in Eingeschlossene Dateien oder Includestruktur auf eine Datei, oder drücken Sie die EINGABETASTE, um den Quellcode für diese Datei zu öffnen.
  • Klicken Sie mit der rechten Maustaste auf eine Headerdatei, um diese Datei in der anderen Ansicht zu finden. Klicken Sie z. B. in der Ansicht Eingeschlossene Dateien mit der rechten Maustaste auf winrtHeaders.h, und wählen Sie In Includestrukturansicht anzeigen aus, um sie in der Ansicht Includestruktur anzuzeigen.

Screenshot eines Rechtsklicks auf eine Datei in der Ansicht „Eingeschlossene Dateien“. Die Menüoption „In Includestrukturansicht anzeigen“ ist hervorgehoben.

Sie können auch mit der rechten Maustaste auf eine Datei in der Ansicht Includestruktur klicken, um in der Ansicht Eingeschlossene Dateien zu dieser zu springen.

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 Ansichten Eingeschlossene Dateien und Includestruktur enthalten ein Filterfeld, um nach relevanten Headerdateien zu suchen. Es erfolgt ein Teilabgleich für den eingegebenen Namen.
  • Manchmal unterscheidet sich die Parsedauer, die für eine Headerdatei gemeldet wird, je nachdem, welche Datei sie einschließt. Dies kann auf das Zusammenspiel verschiedener #define-Anweisungen zurückzuführen sein, das sich darauf auswirkt, welche Teile des Headers erweitert werden, sowie auf die Dateizwischenspeicherung und andere Systemfaktoren.
  • Wenn Sie nicht verstehen, was die Ansicht Eingeschlossene Dateien oder Includestruktur vermitteln möchte, zeigen Sie mit der Maus auf die Registerkarte, um eine QuickInfo mit einer Beschreibung anzuzeigen. Wenn Sie beispielsweise auf die Registerkarte Includestruktur zeigen, lautet die QuickInfo: „Ansicht mit Includestatistiken für jede Datei, in der die untergeordneten Knoten die Dateien sind, die vom übergeordneten Knoten eingeschlossen werden“.
  • Möglicherweise gibt es Fälle (z. B. Windows.h), in denen die aggregierte Dauer aller Zeiten für eine Headerdatei länger als die Dauer des gesamten Builds ist. Das liegt daran, dass Header gleichzeitig in mehreren Threads geparst werden. Wenn zwei Threads gleichzeitig eine Sekunde benötigen, um eine Headerdatei zu parsen, beträgt die Buildzeit zwei Sekunden, obwohl die Wall Clock Time nur eine Sekunde betragen hat. Weitere Informationen finden Sie unter Wall Clock Responsibility Time (WCTR).

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 die relevante Headerdatei nicht in der Ansicht Eingeschlossene Dateien oder Includestruktur erscheint, wurde sie entweder nicht erstellt oder die Buildzeit ist zu unerheblich, um aufgeführt zu werden.

Weitere Informationen

Vergleichen von Headereinheiten, Modulen und vorkompilierten Headern
Video: Build Insights in Visual Studio – Pure Virtual C++ 2023
Faster C++ builds, simplified: a new metric for time (Schnellere C++-Builds vereinfacht: eine neue Metrik für die Zeit)
Tutorial: Problembehandlung für Inlinefunktionen zur Buildzeit
Tutorial: vcperf und Windows Performance Analyzer