C++的編譯連結與在vs中build提速

2023-09-04 12:01:07

通過gcc或msvc,clang等編譯器編譯出來的C++原始檔是.o檔案。在windows上也就是PE檔案,linux為ELF檔案,在這一步中,呼叫其它程式碼檔案中的函數的函數地址是未知的(00000),等到連結之後才會替換掉函數地址的

linux,windows 可執行檔案(ELF、PE)

C++是如何編譯的

C/C++編譯過程主要分為4個過程

  1. 編譯預處理
  2. 編譯、優化階段
  3. 組合過程
  4. 連結程式

編譯遊戲引擎的耗時

內網使用IB(incrediBuild)編譯引擎時總耗時2分23秒,編譯2分鐘,link耗時15秒

在vs中提高c++的編譯速度

達到修改一行程式碼,10s內編譯完,link會花點時間,因為所的工程都是lib,而不是dll,如果改成dll,則會更快。

偵錯資訊的格式

把所有的工程的屬性這項: C/C++ - General - Debug Information Format ,改成:C7 compatible (/Z7)

實際上是在vcxproj檔案中增加了這樣一項:<DebugInformationFormat>OldStyle</DebugInformationFormat>

Debug Information Format是一個編譯器選項,用於控制生成的偵錯資訊的格式。

偵錯資訊是一種用於偵錯程式的資料,包括變數名、函數名、行號等資訊。在程式出現錯誤時,偵錯資訊可以幫助開發人員快速定位問題。

Debug Information Format選項有以下幾種可選值:

  1. None:不生成偵錯資訊。

  2. Program Database (/Zi):生成一個獨立的PDB檔案,包含所有的偵錯資訊。

  3. Program Database for Edit and Continue (/ZI):生成一個獨立的PDB檔案,包含所有的偵錯資訊,並且支援編輯和繼續偵錯。

  4. Old Style (/Z7):將偵錯資訊嵌入到可執行檔案中。

需要注意的是,生成偵錯資訊會增加可執行檔案的大小,因此在釋出版本時應該關閉偵錯資訊生成。

另外,需要注意的是,如果使用了/DEBUG選項,那麼編譯器會自動將Debug Information Format選項設定為Program Database (/Zi)。

預編譯頭

選中工程,右鍵 - 屬性 - C/C++ - Precompiled Headers - Precompiled Header 改成 Not

就是把一些固定的東西先編譯好,其他cpp檔案直接參照就不copy了,這東西在分散式下沒用, 單機是有效果的

什麼是預編譯頭?

includeN多的標頭檔案會導致編譯變慢,提取整個專案公共標頭檔案放到一起,只編譯一次,減少編譯時間。

增量編譯

在入口工程啟用增量編譯 : Linker - General - Enable Incremental Linking,勾選:Yes (/INCREMENTAL)

Linker - Optimization

右鍵 - 屬性,Linker - Optimization - 把這2項改成No

  • References :No (/OPT:NOREF)
  • Enable COMDAT Folding :No (/OPT:NOICE)

OptimizeReferences 用於控制是否優化未使用的函數和資料的程式碼生成,當OptimizeReferences選項設定為/OPT:REF時,編譯器將在連結時刪除未使用的函數和資料,以減小可執行檔案的大小。這可以減少可執行檔案的大小,提高程式的執行效率。

EnableCOMDATFolding 用於控制是否啟用COMDAT摺疊優化,當Enable COMDAT Folding選項設定為Yes/OPT:ICF時,編譯器將啟用COMDAT摺疊優化。這可以減少可執行檔案的大小,提高程式的執行效率。

Linker - Debugging

右鍵 - 屬性, Linker - Debugging , Generate Debug Info 改成Faster,可以link的更快

cgthreads(Code generation threads)

cl 預設使用的執行緒數是 4 ,最大可設定成 8 ,如果擁有更多核心時設定為8將可以縮短構建時間,在開啟GL時效果更佳

在專案 設定屬性 > C/C++ > 命令列 增加 /cgthreads8

MP(Build with multiple processes)

當您編譯許多檔案時,編譯器選項可以顯著減少構建時間。為了縮短構建時間,編譯器會建立最多processMax自身的副本,然後同時使用這些副本來編譯原始檔

其他模式建議開啟, published 模式 , 測試後構建時間並無明顯差異, 因為MP對連結時編譯並不能起到提速作用

同樣也是在專案 設定屬性 > C/C++ > 命令列 增加 /mp

Incredibuild(叢集編譯)

使用ib編譯完之後,再從vs按F5即可啟動偵錯,已經生成了pdb檔案。

非published模式(非WPO模式) 建議以下設定:

開啟IB,切到Visual Studio Builds - Advanced

  1. 關閉 Limit Concurrent PDB file Instances to []
  2. 開啟 Force 64-bit tooset

使用叢集或者限制本機cpu的核數,這倆動態控制好,但是本機使用一半的核還是會卡,因為其它程序不一定會分配到空閒的CPU

開啟IB,切到Initiator - General

  1. Avoid task execution on local machine when possible(儘可能避免在本地計算機上執行任務)

  2. CPU Allocation : Limit maximum number of cores utilized in build to (限制構建過程中可使用的最大核心數為)

WPO

全程式優化(Whole program optimization) 功能,是為了增加檔案之間的可見性,將編譯延遲到了連結時

WPO 可以提高程式的執行效能,一般在釋出模式下都會開啟此功能,代價只是增加了部分構建時長

如果開啟WPO模式後, 不建議使用 IB 構建, 也許可能會有未知問題

makefile

摘自UE引擎的某個makefile範例

CXXFLAGS += -std=c++11 -Wall -Wextra -pedantic -Wcast-align -Wcast-qual -Wno-ctor-dtor-privacy -Wdisabled-optimization -Wformat=2 -Winit-self -Wmissing-declarations -Wmissing-include-dirs -Wold-style-cast -Woverloaded-virtual -Wredundant-decls -Wshadow -Wsign-conversion -Wsign-promo -Wstrict-overflow=5 -Wswitch -Wundef -Wno-unused -Wnon-virtual-dtor -Wreorder -Wdeprecated -Wno-float-equal
CPPFLAGS += -I ../single_include -I . -I thirdparty/doctest -I thirdparty/fifo_map -DDOCTEST_CONFIG_SUPER_FAST_ASSERTS

SOURCES = src/unit.cpp \
          src/unit-algorithms.cpp \
OBJECTS = $(SOURCES:.cpp=.o)

TESTCASES = $(patsubst src/unit-%.cpp,test-%,$(wildcard src/unit-*.cpp))          

CMake

cmake.txt範例

cmake_minimum_required(VERSION 3.17)
project(mycpp)

set(CMAKE_CXX_STANDARD 11)
#新增需要編譯的檔案
add_executable(strTest strTest.cpp)

C/C++為什麼要寫標頭檔案?

來源: 為什麼C/C++要分為標頭檔案和原始檔? - 知乎 (zhihu.com)

C時代的時候編譯器比較簡單,是固定的編譯和連結兩個過程,編譯一次只處理一個檔案,進行預處理之後,標頭檔案會插入到這一個檔案裡,不同原始碼檔案的處理時獨立的,這樣如果標頭檔案裡面定義了一個函數的實現,編譯的時候所有參照這個標頭檔案的原始碼檔案,生成的obj裡都會有這個符號。而連結是通用的連結程式,從組合時代就用的工具,沒有什麼高階功能,同一個符號連結時出現兩次是會報錯的。

但是,我們又說了,每個檔案的編譯是獨立的,所以如果實現不在當前原始檔裡面,呼叫的時候編譯器就不知道這個函數的型別和簽名,沒法生成呼叫程式碼,所以必須在呼叫之前先宣告一遍。如果不把宣告寫在標頭檔案裡面,就必須在每個用到這個函數的原始檔裡都宣告一遍,很不方便,所以綜合之後的解決方案就是實現寫原始碼檔案裡面,宣告寫標頭檔案裡面。