如何加快 C++ 原始碼的編譯速度


本文轉載自《如何加快C++代码的编译速度》,算是滿久以前的文章。
Heresy 自己有再根據自己的理解,重新寫一次。

首先,為什麼自己寫出來的程式碼、或是大型專案的編譯速度會很慢?其中一個原因,是因為 C++ 的「程式碼檔」(source file 一般都是 .cpp)和「標頭檔」(header file、一般是 .h)的架構;這個架構的基本概念是:

  1. 編譯器只會去編譯 source file,把它編譯成物件檔(obj 檔),最後在連結(link)起來。
  2. Source file 內有用到 header file,都會被讀取出來、和 source file 一起被編譯。

而在這樣的架構下,要加快編譯的速度,方法有:

  1. 從原始碼的角度來看

    • 在 header file 裡面減少 include 其他 header file、盡量使用 forward declaration(維基百科

      因為如果 header file A.h 有 include 其他 header file 的話,編譯器要編譯有 include A.h 的 source file 的時候,就必須要把 A.h 所有 include 的 header file 全部分析過一遍、把它拿來和 source file 一起編譯。而當其中一個 header file 有做過修改的話,這個 source file 就算沒有用到修改的部分,也會需要重新編譯。

      所以,可以的話,盡量把 include 這個動作,放到 source file 裡,並且只有在真正需要的時候,才去做 header file 的 include;如此一來不但可以減少實際上要編譯的程式碼數量,也可以減少需要重新編譯的頻率。

    • 使用 Private Implementation,把函式的實作從 header file 拿到 source file

      基本上,就是讓 header file 裡面,只有留下 class 或是 function 的定義,相關的實作都盡量不要寫在 header file、而是寫在 source file。如此一來,除了 header file 看起來會比較簡潔外,如果是定義不變、只是修改實作的內容的話,就只會修改到 source file、而不會修改到 header file;而這樣的話,其他 include 到這個 header file 的 source file,也就不需要重新編譯了~

      但是另一方面,如果是要使用 inline function 或是 template 的話,就有可能沒辦法這樣做;這點就是需要取捨的了。

    • 高度模組化

      基本上,就是盡量減少檔案與檔案、以及專案和專案之間的關聯性。以檔案來說,應該讓每個 header file 裡的功能單一化、不要過於複雜、多元,如此應該可以讓修改 header file 後會影響到的檔案數量盡量變少、只影響到真正該影響的 source file。而在專案的部分,雖然專案間可能會有相依性,但是也應該讓影響盡量地小,盡量不要變成說改了一個專案、結果另一個專案也需要跟著重新編譯;比較理想的狀況,應該是除非修改了介面、不然只需要重新連結。

      基本上,這部份的工作和前兩者算是有關的,但是差異在於,這點著重於「設計」的層面;也就是透過設計,來讓每個檔案、專案修改時造成的影響盡量地小。

  2. 綜合技巧

    • 減少指定的 Include 資料夾

      很單純的一個狀況。編譯器會在指定的 Include 資料夾(Additional Include Directories)裡面、去找所需要的 header file;當所給的資料夾過多的時候,編譯器有可能會花更多的時間、來尋找所要求的 header 檔到底在哪裡。所以避免告訴編譯器不需要的 Include 資料夾,也可以加快編譯時的前處理速度。

    • 使用預先編譯好的 Header File

      它的名稱是「Precompiled Header」,一般是簡稱為「PCH」。雖然一般的 header file 是不會直接被編譯的,不過 PCH 的機制就是把經常使用、但是基本上不會去修改到的 Header file,都先做預先編譯的動作;這樣其他 source file 要使用的時候,就可以很快速地使用,而不需要每次 include 都得重新去分析、編譯他了~詳細可以參考維基百科、或 MSDN 的介紹

    • Unity Builds

      「Unity Builds」的方法,基本上就是用一個 source file(例如 all.cpp)把所有的 source file include 進來,然後不要個別編譯單獨 source file,只去編譯那個包含所有 source file 的檔案(all.cpp)。如此一來,可以減少產生出來的 object file 的數量、以減少最後 linking 時的檔案存取的負擔,並大幅加快處理的速度。(參考《The Magic of Unity Builds》)

      不過這個做法要注意的是,這個做法對編譯器來說,其實就是變成是在編譯一個超大的檔案;而如果本來在不同的 source file 裡面,有同名的全域變數、函式的話,是會產生衝突了~另外,在這情況下,修改單一 source file 也會造成所有的 source file 的內容都需要重新編譯,所以應該也是比較適用於不常修改的 source file。

  3. 硬體資源

    • 平行編譯

      使用多核心處理器,然後編譯環境設定好,就可以同時編譯多個檔案,減少整個專案所需的建置時間。
      以 Microsoft Visula C++ 來說,在可能的情況下,預設應該就會使用所有的 CPU 核心來做平行編譯;而 Linux 的 GNU Make 系統,一般應該是透過「-j」這個參數,來指定要分成幾個工作來平行進行。

    • 更好的磁碟

      編譯的速度慢、很有可能是因為檔案存取的速度造成的瓶頸;所以除了盡量減少磁碟的存取外,也可以透過更換儲存裝置、來加快編譯時的檔案存取速度,例如使用更好的硬碟、RAID、或是 SSD。

    • 分散式編譯

      用多台電腦來進行編譯。個人覺得除非編譯時間真的久到一定程度、而且手邊可用的機器又夠多,不然用到機會不大就是了。

另外,該文還有介紹一本《大型 C++ 軟體設計 (Large-Scale C++ Software Design)》(連結),或許也是可以參考看看的。

對「如何加快 C++ 原始碼的編譯速度」的想法

    • 感謝提供相關資訊~

      不過,其實 Heresy 自己的經驗,如果都開最佳化的話,gcc 的 linkung 也很慢…

Chih-Min Chao 發表迴響 取消回覆

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Google photo

您的留言將使用 Google 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.