共用方式為


教學課程:針對組建時間內嵌的函式進行疑難排解

使用 Build Insights Functions 檢視來疑難排解 C++ 專案中函式內嵌對組建時間的影響。

必要條件

  • Visual Studio 2022 17.8 或更新版本。
  • 如果您安裝 [使用 C++ 進行桌面開發] 工作負載或 [使用 C++ 進行遊戲開發] 工作負載,則預設會啟用 C++ Build Insights。

Visual Studio 安裝程式的螢幕擷取畫面,其中已選取 [使用 C++ 進行桌面開發] 工作負載。

會顯示已安裝元件的清單。 C++ Build Insights 會被反白並選取,這表示它已安裝。

Visual Studio 安裝程式的螢幕擷取畫面,其中已選取 [使用 C++ 進行遊戲開發] 工作負載。

會顯示已安裝元件的清單。 C++ Build Insights 會被反白並選取,這表示它已安裝。

概觀

Build Insights 現已整合到 Visual Studio 中,可協助您最佳化組建時間,特別是針對 AAA 遊戲等大型專案。 Build Insights 提供了 Functions 檢視等分析功能,有助於診斷組建期間昂貴的程式碼產生問題。 它會顯示為每個函式產生程式碼所需的時間,並顯示 __forceinline 的影響。

__forceinline 指示詞會指示編譯器內嵌函式,不論其大小或複雜度為何。 內嵌函式可藉由減少呼叫函式的額外負荷來改善執行階段效能。 代價是它會增加二進位檔的大小並影響您的組建時間。

對於最佳化的組建,產生程式碼所花費的時間對總組建時間影響很大。 一般而言,C++ 函式最佳化會快速進行。 在特殊情況下,某些函式可能會變得夠大且足夠複雜,進而給最佳化工具帶來壓力並明顯降低您的組建速度。

在本文中,了解如何使用 Build Insights Functions 檢視來尋找組建中的內嵌瓶頸。

設定建置選項

若要測量 __forceinline 的結果,請使用 Release 組建,因為偵錯組建不會內嵌 __forceinline,因為偵錯組建會使用 /Ob0 編譯器開關,這會停用該最佳化。 設定 Releasex64 的組建:

  1. 在 [方案組態] 下拉式清單中,選擇 [Release]
  2. 在 [方案平台] 下拉式清單中,選擇 [x64]

螢幕擷取畫面:[方案組態] 下拉式清單設定為 [Release],而 [方案平台] 下拉式清單設定為 x64。

將最佳化層級設定為最佳化上限:

  1. 方案總管中,以滑鼠右鍵按一下專案名稱,然後選取 [屬性]

  2. 在專案屬性中,瀏覽至 C/C++>最佳化

  3. 將 [最佳化] 下拉式清單設定為 [最佳化上限 (偏好的速度)(/O2)]。

    專案屬性頁對話方塊的螢幕擷取畫面。這些設定會在 [組態屬性 > C/C++ > 最佳化] 下公開。[最佳化] 下拉式清單設定為 [最佳化上限 (偏好的速度)(/O2)]。

  4. 按一下 [確定] 關閉對話方塊。

執行 Build Insights

在您選擇 (及使用上一節中所設定的 [Release] 組建選項) 的專案上,透過從主功能表中選擇 [組建]>[在選取項目上執行 Build Insights]>[重建] 來執行 Build Insights。 您也可以在 [方案總管] 中以滑鼠右鍵按一下專案,然後選擇 [執行 Build Insights]>[重建]。 選擇 [重建] (而不是 [組建]) 來測量整個專案的組建時間,而不僅僅是目前可能已被修改的幾個檔案的組建時間。

主功能表的螢幕擷取畫面,其中已選取 [在選取項目上執行 Build Insights] > [重建]。

組建完成時,就會開啟事件追蹤記錄 (ETL) 檔案。 它會儲存在 Windows TEMP 環境變數所指向的資料夾中。 產生的名稱是基於收集時間。

函式檢視

在 ETL 檔案的視窗中,選擇 [Functions] 索引標籤。它會顯示已編譯的函式,以及為每個函式產生程式碼所需的時間。 如果為函式產生的程式碼量可以忽略不計,則它將不會出現在清單中,以避免降低建置事件收集效能。

[Build Insights Functions] 檢視檔案的螢幕擷取畫面。

在 [函式名稱] 資料行中,performPhysicsCalculations() 會反白並標示著火圖示。:::

時間 [秒,%] 資料行會顯示在掛鐘責任時間 (WCTR) 中編譯每個函式所花費的時間。 此計量會根據其平行編譯器執行緒的使用情況在函式之間分配掛鐘時間。 例如,如果兩個不同的執行緒在一秒鐘內同時編譯兩個不同的函式,則每個函式的 WCTR 會記錄為 0.5 秒。 這反映了每個函式在總編譯時間中所佔的比例,並考慮到每個函式在平行執行期間所消耗的資源。 因此,WCTR 可更妥善地測量每個函式對同時發生多個編譯活動的環境中的整體組建時間的影響。

Forceinline 大小資料行會顯示為函式所產生的大致指令數。 按一下函式名稱前的 >形箭號,以查看該函式中所展開的個別內嵌函式,以及每個內嵌函式所產生的指令的大致數目。

您可以按一下時間資料行來對清單進行排序,以查看哪些函式的編譯時間最長。 「火」圖示表示產生該函式的成本很高,值得調查研究。 過度使用 __forceinline 函式可能會大幅降低編譯速度。

您可以使用 [篩選函式] 方塊來搜尋特定的函式。 如果函式的程式碼產生時間太短,則它不會出現在 Functions 檢視中。

透過調整函式內嵌來縮短組建時間

在此範例中,performPhysicsCalculations 函式的編譯時間最長。

[Build Insights Functions] 檢視的螢幕擷取畫面。

在 [函式名稱] 資料行中,performPhysicsCalculations() 會反白並標示著火圖示。

透過進一步調查,選取該函式前的 >形箭號,然後將 Forceinline 大小資料行從最高到最低進行排序,我們發現了導致該問題的最大因素。

[Build Insights Functions] 檢視的螢幕擷取畫面,其中包含展開的函式。

performPhysicsCalculations() 已展開,並顯示一長串內嵌在其中的函式。 顯示了多個函式執行個體,例如 complexOperation()、recursiveHelper() 和 sin()。 [Forceinline 大小] 資料行顯示 complexOperation() 是最大的內嵌函式,有 315 個指令。 recursiveHelper() 有 119 個指令。 Sin() 有 75 個指令,但它的執行個體比其他函式多得多。

有一些較大的內嵌函式 (例如 Vector2D<float>::complexOperation()Vector2D<float>::recursiveHelper()) 會導致此問題。 但 Vector2d<float>::sin(float)Vector2d<float>::cos(float)Vector2D<float>::power(float,int)Vector2D<float>::factorial(int) 的執行個體還有很多 (此處未全部顯示)。 當您把它們加起來時,產生的指令總數很快就超過了幾個較大的產生的函式。

查看原始程式碼中的這些函式,我們發現執行時間將花費在迴圈內。 例如,以下是 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;
}

相較於函式本身的成本,呼叫此函式的整體成本也許微不足道。 將函式設為內嵌函式最有利的情況是,當呼叫函式 (將引數推入堆疊、跳到函式、彈出返回引數以及從函式返回) 所需的時間與執行函式所需的時間大致相似時,以及當函式被多次呼叫時。 如果情況並非如此,則使其內嵌的回報可能會減少。 我們可以嘗試從其中移除 __forceinline 指示詞,以查看它是否有助於縮短組建時間。 powersin()cos() 的程式碼相似,因為程式碼是由一個將執行多次的迴圈所組成。 我們也可以嘗試從這些函式中移除 __forceinline 指示詞。

我們從主功能表中選擇 [組建]>[在選取項目上執行 Build Insights]>[重建],以重新執行 Build Insights。 您也可以在 [方案總管] 中以滑鼠右鍵按一下專案,然後選擇 [執行 Build Insights]>[重建]。 我們像以前一樣選擇 [重建] (而不是 [組建]) 來測量整個專案的組建時間,而不僅僅是目前可能已被修改的幾個檔案的組建時間。

組建時間從 25.181 秒變為 13.376 秒,且 performPhysicsCalculations 函式不再顯示在 Functions 檢視中,因為它對組建時間的貢獻不足以進行計數。

2D 向量標頭檔的螢幕擷取畫面。

在 [函式名稱] 資料行中,performPhysicsCalculations() 被反白並標示著火圖示。:::

「診斷工作階段時間」是組建所花費的總時間,加上收集 Build Insights 資料的任何額外負荷的時間。

下一步是對應用程式進行分析,以查看應用程式的效能是否受到變更的負面影響。 如果是,我們可以視需要選擇性地將 __forceinline 加回去。

Functions 檢視中的檔案上按兩下、以滑鼠右鍵按一下或按 Enter 鍵,以開啟該檔案的原始程式碼。

在 [Functions] 檢視中以滑鼠右鍵按一下檔案的螢幕擷取畫面。功能表選項 [移至來源檔案] 被反白。

提示

  • 您可以選取 [檔案]>[另存新檔] 來將該 ETL 檔案存到更永久的位置,以保留組建時間的記錄。 然後,您可以將它與未來的組建進行比較,以查看您的變更是否縮短了組建時間。
  • 如果您不小心關閉 [Build Insights] 視窗,請在暫存資料夾中尋找 <dateandtime>.etl 檔案來重新開啟它。 TEMP Windows 環境變數提供了您的暫存檔資料夾的路徑。
  • 若要使用「Windows 效能分析器 (WPA)」深入了解 Build Insights 資料,請按一下 ETL 視窗右下角的 [在 WPA 中開啟] 按鈕
  • 拖曳資料行以變更資料行的順序。 例如,您可能偏好將時間資料行移至第一個資料行。 您可以以滑鼠右鍵按一下資料行標題並取消選取您不想看到的資料行,以隱藏資料行。
  • Functions 檢視提供了一個篩選方塊來尋找您感興趣的函式。 它會對您所提供的名稱進行部分比對。
  • 如果您忘記如何解釋 Functions 檢視嘗試向您顯示的內容,請將滑鼠停留在索引標籤上以查看描述該檢視的工具提示。 如果您將滑鼠停留在 Functions 索引標籤上,工具提示會顯示:「顯示其中子節點為強制內嵌函式之函式的統計資料檢視」。

疑難排解

  • 如果 [Build Insights] 視窗未出現,請執行重建,而不是組建。 如果沒有實際組建任何內容,則不會出現 [Build Insights] 視窗;如果自上次組建以來沒有檔案變更,則可能會發生這種情況。
  • 如果 [Functions] 檢視未顯示任何函式,則您可能沒有使用正確的最佳化設定進行組建。 確定您正在使用完整的最佳化來組建 Release,如 設定組建選項中所述。 此外,如果函式的程式碼產生時間太短,它也不會出現在清單中。

另請參閱

內嵌函式 (C++)
更快速的 C++ 組建,簡化:新的時間計量
Visual Studio 中的 Build Insights 影片 - Pure Virtual C++ 2023
疑難排解標頭檔對組建時間的影響
Visual Studio 2022 17.8 中 Build Insights 的 Functions 檢視
教學課程:vcperf 和 Windows 效能分析器
使用 C++ Build Insights 來縮短程式碼產生時間