0%

Story about C/C++ compiling model

Coroutine的文章太難寫了,只好先寫篇簡單的
這是一篇工程性的文章,給對這方面有興趣的人

Motivation

由於看了某AI部門寫的C++服务编译耗时优化原理及实践這篇文章,想要分享一下思路 ,不過編譯二十分鐘就在哀哀叫 (當初我前公司動輒一兩個小時

Clarification

  • 這問題跟C++無關,這是C語言的問題
  • C++的編譯模型跟C語言一樣(Before C++20)
  • 不過由於C++有template和header only libraries而將這問題放大了很多倍
  • 不信的話可以試試看編譯Linux kernel

Root Cause

哪有什麼Root Cause,這不是個Bug

只是跟不上時代

C語言誕生在1969-1973,至今超過五十年了
當初計算機能力比不上現代,因此產生了
Header / Implementation Separation的做法
C++繼承了這個Compiling Model

Modern Language怎麼做

將宣告和實做擺在一起

  • Application Language: Java/C#/Javascript/Golang
  • System Language: Zig/Rust

About Future

分兩方面來說

C

C語言已經是個Inactive的語言了

  • 自從C11之後,不加入重大新功能,只做相容性改善
  • 別期待它會加入Module功能

C++

相較於C,C++從11之後努力追趕Modern Language的路線

  • 直到C++20之後,才推出正式的Module Spec
    • 不過何時可以用上未可知
    • Cloud端也許明年就能使用,embedded等vendor改朝換代不知道要多久
    • 就算全面普及之後,ecosystem也要一段時間才能趕上
    • 以Javascript為例,從commonJS切到Module也走了五六年

What can we do now

未來會怎麼發展不知道,不過現在有幾個選項

什麼都不要做

這不是Bug,不管它也無妨,如果真在意編譯速度的話,當初我前公司買Incredibuild做分散式編譯
根據最前面那篇文章的數據,使用分散式編譯效果比其他方法都有用

Precompiled Headers

將常用的header通通include在一起,然後編譯這個大的header file
有興趣可以參考Using pre-compiled headers in GCC/Clang using CMake and usage in Catch2

Reduce Header dependency

這可以分成幾方面來討論

Find unused header

舉個例子

1
2
#include <stdio.h>
int add(int a, int b) { return a + b; }

stdio.h在這邊就是完全沒必要的,如果是goalng,import fmt然後沒用到根本編譯不過,不過golang這作法是個雙面刃,我不喜歡

如果要工具的話可以考慮[include-what-you-use](

Choose suitable third party libraries

不可能所有東西都自己寫,當你需要某個功能的時候,先找找是否有適合的選項,問題是如果選擇太多了該怎麼辦,總不能

文中討論到Boost,Boost最大的問題是它依賴性太重了
當你需要一台腳踏車的廠警,選擇一台坦克車絕對不是個好主意

Opaque Pointer

俗稱編譯防火牆的技術,是當的斷開header dependency
以C語言來說,header大概長這樣

1
2
3
4
5
struct obj;

size_t obj_size(void);
void obj_setid(struct obj *, int);
int obj_getid(struct obj *);

implementation是這樣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "obj.h"

struct obj {
int id;
};

size_t obj_size(void) {
return sizeof(struct obj);
}

void obj_setid(struct obj *o, int i) {
o->id = i;
}

int obj_getid(struct obj *o) {return o->id; }

C++版的叫pImpl,如果有看過我的程式碼應該不陌生,掠過

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <memory>

class PublicClass {
public:
PublicClass(); // Constructor
PublicClass(const PublicClass&); // Copy constructor
PublicClass(PublicClass&&); // Move constructor
PublicClass& operator=(const PublicClass&); // Copy assignment operator
PublicClass& operator=(PublicClass&&); // Move assignment operator
~PublicClass(); // Destructor

// Other operations...

private:
struct CheshireCat; // Not defined here
std::unique_ptr<CheshireCat> d_ptr_; // Opaque pointer
};

Conclusion

雖然這不是個Bug,不過有人題就表示這是個需求,未來會怎麼走沒人知道,只能現有的材料下能做些什麼改進