疑難排解標頭檔對組建時間的影響
使用 Build Insights 的 [包含的檔案] 和 [包含樹狀結構] 檢視,對 #include
檔案在 C 和 C++ 建置時間方面的影響進行疑難排解。
必要條件
- Visual Studio 2022 17.8 或更新版本。
- 如果您使用 Visual Studio 安裝程式安裝了「使用 C++ 的桌面開發」工作負載,依預設會啟用 C++ Build Insights:
顯示已安裝的元件清單。 醒目提示並選取 C++ Build Insights,表示已安裝。
或「使用 C++ 的遊戲開發」工作負載:
顯示已安裝的元件清單。 醒目提示並選取 C++ Build Insights,表示已安裝。
概觀
Build Insights 現已整合到 Visual Studio 中,可協助您最佳化建置時間,特別是針對 AAA 遊戲等大型專案。 剖析大型標頭檔時會影響到建置時間,尤其是反覆剖析時。
Build Insights 會在 [包含的檔案] 檢視中提供分析,以利診斷在專案中剖析 #include
檔案的影響。 其中顯示剖析每個標頭檔所需的時間,以及標頭檔之間關聯性的檢視。
在本文中,您可以了解如何使用 Build Insights 的 [包含的檔案] 和 [包含樹狀結構] 檢視來識別剖析成本最高的標頭檔,以及如何建立先行編譯標頭檔以達到最理想的建置時間。
設定建置選項
在收集 Build Insights 資料之前,請為您要測量的組建類型設定建置選項。 例如,如果您對 x64 偵錯建置時間有所顧慮,請設定 [偵錯] 和 [x64] 的建置:
在 [解決方案設定] 下拉式清單中,選擇 [偵錯]。
在 [解決方案平台] 下拉式清單中,選擇 [x64]。
[解決方案設定] 下拉式清單隨即顯示。 其中包含 [偵錯]、[發行] 和 [設定] 管理員的選項。 [解決方案平台] 下拉式清單會設定為 [x64]。
執行 Build Insights
在您選擇的專案上,並使用上一節中設定的偵錯組建選項,從主功能表選擇 [建置專案名稱>>重建] 上的 <[建>置執行組建深入解析] 來執行 Build Insights。 您也可以在方案總管中以滑鼠右鍵按一下專案,然後選擇 [執行 Build Insights]>[重建]。 選擇 [重建] (而不是 [建置]) 來測量整個專案的建置時間,而非僅止於目前可能已修改的幾個檔案。
建置完成時,會開啟事件追蹤記錄 (ETL) 檔案。 該檔案會儲存在 Windows TEMP
環境變數所指向的資料夾中。 產生的名稱以收集時間為基礎。
包含的檔案檢視
追蹤檔案會顯示建置時間,在此範例中為 16.404 秒。 [診斷工作階段] 是執行 Build Insights 工作階段所需的總時間。 選擇 [包含的檔案] 索引標籤。
此檢視會顯示處理 #include
檔案所花費的時間。
檔案路徑資料行中醒目提示了數個附有火焰圖示的檔案,因為這些檔案耗費了 10% 以上的建置時間進行剖析。 winrtHeaders.h 是最大的一個,耗時 8.581 秒 (或佔建置時間 16.404 秒的 52.3%)。
[檔案路徑] 資料行中的某些檔案旁會有火焰圖示,表示這些檔案佔用了 10% 或更多建置時間。
時間 [秒,%] 資料行會顯示在時鐘責任時間 (WCTR) 中編譯每個函式所花費的時間。 此計量會根據檔案使用平行執行緒的情形,來分配剖析檔案所需的時鐘時間。 例如,如果兩個不同的執行緒在一秒的期間內同時剖析兩個不同的檔案,則每個檔案的 WCTR 會記錄為 0.5 秒。 這反映了每個檔案在總編譯時間中所佔的比例,並考量到每個檔案在平行執行期間耗用的資源。 因此,WCTR 可更妥善地測量每個檔案在同時發生多個編譯活動的環境中對於整體建置時間的影響。
[剖析計數] 資料行會顯示標頭檔的剖析次數。
此清單中醒目提示的第一個標頭檔是 winrtHeaders.h
,耗時 8.581 秒 (或佔整體建置時間 16.404 秒的 52.3%)。 成本次高的是 Windows.UI.Xaml.Interop.h
,再次之為 Windows.Xaml.h
。
若要查看包含 winrtHeaders.h
的檔案,請按一下旁邊的箭號。 [剖析計數] 資料行有助於指出標頭檔包含在其他檔案內的次數。 一個標頭檔有可能被包含多次,這表示該檔案很適合作為先行編譯標頭檔或用於重構。
[編譯單位] 資料行會顯示在處理包含的檔案時所處理的檔案。 在此範例中,在編譯 Grapher.cpp
時包含 winrtHeaders.h
:
此範例 ETL 檔案顯示範例專案的包含檔案。 在檔案路徑資料行中,已選取並展開 winrtHeaders.h。 建置需要 8.219 秒的時間,這是建置時間的 50.1%。 其子節點 Grapher.cpp,也會列為編譯單位。
若一個標頭檔被包含多次,且您想要了解這最常發生在何處,編譯單位資料行將有助於釐清編譯了哪個檔案。
我們知道 winrtHeaders.h
的剖析成本很高,但藉此將可獲得更多資訊。
包含樹狀結構檢視
在此檢視中,子節點是父節點所包含的檔案。 這可協助您了解標頭檔之間的關聯性,並找機會減少剖析標頭檔的次數。
選取 ETL 檔案中的 [包含樹狀結構] 索引標籤,以查看 [包含樹狀結構] 檢視:
顯示專案的包含樹狀結構。 檔案路徑資料行中會列出每個包含其他檔案的檔案,及其包含的檔案數目,和剖析檔案的時間。
在此檢視中,[檔案路徑] 資料行會顯示每個包含其他檔案的檔案。 [包含計數] 會列出此標頭檔包含的檔案數目。 列出剖析此檔案的時間,且展開時會列出對此標標頭檔包含的每個標頭檔進行剖析的時間。
先前,我們看到剖析 winrtHeaders.h
非常耗時。 如果我們在 [篩選檔案] 文字方塊中輸入 winrtHeaders.h
,將可篩選檢視而僅顯示名稱中包含 winrtHeaders.h
的項目。 按一下 winrtHeaders.h
旁的箭號,會顯示其包含的檔案:
檔案路徑資料行會列出每個包含其他檔案的檔案,及其包含的檔案數目,和剖析檔案的時間。已選取並展開 winrtHeaders.h 以顯示其包含的檔案。 Windows.UI.Xaml.Interop.h 是其中一個檔案,展開後顯示了 Windows.UI.Xaml.Interop.h,而此檔案也已展開而顯示其包含的標頭檔。
我們看到 winrtHeaders.h
包含 Windows.UI.Xaml.Interop.h
。 請記住,在 [包含的檔案] 檢視中,剖析也是很耗時的。 按一下 Windows.UI.Xaml.Interop.h
旁邊的箭號可以看到其中包含 Windows.UI.Xaml.h
,內含 21 個其他標頭檔,其中兩個也列於熱門清單上。
找出剖析成本最高的一些標頭檔,並確認應由 winrtHeaders.h
將其導入後,這表示我們可以使用先行編譯的標頭來加快包含 winrtHeaders.h
的速度。
使用先行編譯的標頭改善建置時間
由於我們從 [包含的檔案] 檢視中得知剖析 winrtHeaders.h
相當耗時,而且我們從 [包含樹狀結構] 檢視中得知 winrtHeaders.h
包含數個剖析十分耗時的其他標頭檔,因此我們建置了先行編譯標頭檔 (PCH),藉由將這些檔案一次性地剖析為 PCH,來加快速度。
我們新增了 pch.h
以包含 winrtHeaders.h
,如下所示:
#ifndef CALC_PCH
#define CALC_PCH
#include <winrtHeaders.h>
#endif // CALC_PCH
PCH 檔案必須先編譯才能使用,因此我們在專案中新增了任意命名為 pch.cpp
的檔案,其中包含 pch.h
。 其中一行如下:
#include "pch.h"
然後,我們將專案設定為使用 PCH。 此作業可在專案屬性中完成:透過 [C/C++]>[先行編譯的標頭],並將 [先行編譯的標頭] 設定為 [使用 (/Yu)],[先行編譯標頭檔] 設定為 [pch.h]。
先行編譯的標頭設定為:使用 (/Yu)。 先行編譯標頭檔設定為 pch.h。
為了使用 PCH,我們將其納入為使用 winrtHeaders.h
的來源檔案中的第一行。 它必須位在任何其他包含檔案前面。 或者,為了簡單起見,我們可以修改專案屬性,以在解決方案中的每個檔案開頭包含 pch.h
;為此,請將專案屬性 [C/C++]>[進階]>[強制的包含檔案] 設定為 pch.h
:
強制的包含檔案設定為 pch.h。
由於 PCH 包含 winrtHeaders.h
,我們可以從目前包含此檔案的所有檔案中移除 winrtHeaders.h
。 這並非必要動作,因為編譯器可察覺 winrtHeaders.h
已包含在內,而不會再次加以剖析。 為了清楚起見,有些開發人員傾向於將 #include
保留在來源檔案中,或者,基於 PCH 可能會重構而可能不再包含該標頭檔。
測試變更
首先我們清除專案,以確保我們比較的檔案建置,是與之前相同的檔案。 若要只清除一個專案,請以滑鼠右鍵按一下 [方案總管] 中的專案,然後選擇 [僅限專案]>[僅清除 <prj名稱>]。
由於此專案現在使用先行編譯的標頭 (PCH),我們不想測量建置 PCH 所花費的時間,因為這只會發生一次。 為此,我們載入 pch.cpp
檔案並選擇 Ctrl+F7,而僅建置該檔案。 您也可以在 [方案總管] 中以滑鼠右鍵按一下 pch.cpp
,然後選擇 Compile
,以編譯此檔案。
現在,我們會在 [方案總管] 中重新執行 Build Insights,方法是以滑鼠右鍵按一下專案,然後選擇 [僅限專案]>[在建置時執行 Build Insights]。 您也可以在 [方案總管] 中以滑鼠右鍵按一下專案,然後選擇 [執行 Build Insights]>[建置]。 但這次我們不重建,因為這將會重建我們不想測量的 PCH。 我們稍早清除了專案,這表示會有正常組建編譯我們想要測量的所有專案檔。
當 ETL 檔案出現時,我們看到建置時間從 16.404 秒變成 6.615 秒。 將 winrtHeaders.h
放入篩選方塊中,將不會出現任何項目。 這是因為加以剖析所花費的時間現在可以忽略不計,因為它是由先行編譯的標頭所提取的。
此範例使用先行編譯的標頭,因為這是 C++20 之前的常見解決方案。 但自 C++20 起已有其他更快速、更穩健的方式可包含標頭檔,例如標頭單位和模組。 如需詳細資訊,請參閱比較標頭單位、模組和先行編譯的標頭。
在檢視之間瀏覽
[包含的檔案] 和 [包含樹狀結構] 檢視都有一些導覽功能:
- 按兩下 [包含的檔案] 或 [包含樹狀結構] 中的檔案 [或按 Enter 鍵],可開啟該檔案的原始程式碼。
- 以滑鼠右鍵按一下標頭檔,在其他檢視中尋找該檔案。 例如,在 [包含的檔案] 檢視中,以滑鼠右鍵按一下
winrtHeaders.h
並選擇 [在包含樹狀結構中尋找],可在 [包含樹狀結構] 檢視中查看該檔案。
或者,您可以在 [包含樹狀結構] 檢視中以滑鼠右鍵按一下某個檔案,以在 [包含的檔案] 檢視中跳至該檔案。
提示
- 您可以選取 [檔案]>[另存新檔] 將該 ETL 檔案存到更具持續性的位置,以保留建置時間的記錄。 然後,您可以將其與未來的建置進行比較,以查看您的變更是否縮短了建置時間。
- 如果您不小心關閉 [Build Insights] 視窗,請在暫存資料夾中尋找
<dateandtime>.etl
檔案,以將其重新開啟。TEMP
Windows 環境變數會提供暫存檔資料夾的路徑。 - 若要使用 Windows Performance Analyzer (WPA) 深入了解 Build Insights 資料,請按一下 ETL 視窗右下角的 [在 WPA 中開啟] 按鈕。
- 拖曳資料行以變更資料行的順序。 例如,您可能偏好將 [時間] 資料行移至第一個資料行。 您可以用滑鼠右鍵按一下資料行標頭,並取消選取您不想看到的資料行,以隱藏資料行。
- [包含的檔案] 和 [包含樹狀結構] 檢視提供篩選方塊,用以尋找您感興趣的標頭檔。 這會對您提供的名稱進行部分比對。
- 為標頭檔回報的剖析時間有時會有所不同,端視包含標頭檔的檔案而定。 這可能是因為不同
#define
的交互作用影響到標頭的哪些部分會展開、檔案快取和其他系統因素。 - 如果您忘記了 [包含的檔案] 或 [包含樹狀結構] 檢視所要顯示的內容,請將滑鼠停留在索引標籤上方,以查看描述檢視的工具提示。 例如,如果您將滑鼠停留在 [包含樹狀結構] 索引標籤上方,工具提示會顯示「此檢視會針對子節點是父節點所含檔案的每個檔案顯示包含統計資料」。
- 您可能會看到標頭檔所有時間的彙總持續時間超過整個建置之持續時間的案例 (例如
Windows.h
)。 實際的情況是,標頭同時在多個執行緒上進行剖析。 若有兩個執行緒同時花費一秒剖析一個標頭檔,即使只經過了一秒的時鐘時間,仍應計為 2 秒的建置時間。 如需詳細資訊,請參閱時鐘責任時間 (WCTR)。
疑難排解
- 如果 [Build Insights] 視窗未出現,請執行重建,而非建置。 若實際上未建置任何項目,則不會出現 [Build Insights] 視窗;若自上次建置以來沒有檔案變更,即可能發生這種情況。
- 如果您感興趣的標頭檔未出現在 [包含的檔案] 或 [包含樹狀結構] 檢視中,表示該檔案並未建置,或其建置時間並未重要到需要列出。
另請參閱
建置深入解析秘訣和訣竅
比較標頭單位、模組和先行編譯標頭檔
Visual Studio 中的 Build Insights 影片 - Pure Virtual C++ 2023
更快速的 C++ 組建,簡化:新的時間計量
針對建置時間的函式內嵌進行疑難解答
vcperf 和 Windows 效能分析器