更多精彩內容,請關注微信公衆號:後端技術小屋git
在C語言中,restrict關鍵字用於修飾指針(C99標準)。經過加上restrict關鍵字,編程者可提示編譯器:在該指針的生命週期內,其指向的對象不會被別的指針所引用。github
須要注意的是,在C++中,並沒有明確統一的標準支持restrict關鍵字。可是不少編譯器實現了功能相同的關鍵字,例如gcc和clang中的__restrict關鍵字。編程
那麼restrict關鍵字能給程序的實際運行帶來哪些好處呢?下面舉例說明後端
int add1(int* a, int* b) { *a = 10; *b = 12; return *a + *b; }
你們猜猜add1
函數的返回值是多少?是10 + 12 = 22嗎?性能優化
答案是不必定。在指針a和b的地址不一樣時,返回22沒有問題。可是當指針a與b指向的是同一個int對象時,該對象先被賦值爲10,後被賦值爲12,所以a和b都返回12,所以add1
函數最終返回24微信
使用-O3
優化, add1
對應的彙編代碼以下。能夠看到,在計算返回值時,爲了獲得*a
的值訪問了1次內存,而無論在何種條件下(a == b
or a != b
),*b
的值都是12。所以聰明的編譯器將*a
的值載入eax
寄存器後,直接加上當即數12,而無需再訪問內存獲取*b
的值。在沒法肯定指針a和b是否相同的狀況下,編譯器只能幫你到這裏了.函數
0000000000400a10 <_Z4add1PiS_>: 400a10: c7 07 0a 00 00 00 movl $0xa,(%rdi) ; *a = 10 400a16: c7 06 0c 00 00 00 movl $0xc,(%rsi) ; *b = 10 400a1c: 8b 07 mov (%rdi),%eax ; 結果 = *a 400a1e: 83 c0 0c add $0xc,%eax ; 結果 += 12 400a21: c3 retq
可是若是加上了restrict關鍵字,狀況便大不相同。C/C++和通過-O3優化的彙編代碼以下。經過restrict關鍵字,編譯器依然確認指針a和b不可能指向同一個內存地址,所以在求*a + *b
時,無需訪問內存,由於*a
必然等於當即數10,*b
必然等於當即數12。源碼分析
int add2(int* __restrict a, int* __restrict b) { *a = 10; *b = 12; return *a + *b ; }
0000000000400a30 <_Z4add2PiS_>: 400a30: c7 07 0a 00 00 00 movl $0xa,(%rdi) ; *a = 10 400a36: b8 16 00 00 00 mov $0x16,%eax ; 結果 = 22 400a3b: c7 06 0c 00 00 00 movl $0xc,(%rsi) ; *b = 12 400a41: c3 retq
經過無restrict和有restrict兩種狀況下的彙編指令可看到,後者比前者少訪問一次內存,且少執行一條指令。所以咱們預期有restrict的版本可以得到可觀的性能提高:性能
int main() { int * a = new int; int * b = new int; { std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); for (size_t i=0; i<100000000; i++) add1(a, b); std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); std::cout << "Time difference = " << std::chrono::duration_cast<std::chrono::nanoseconds> (end - begin).count() << "[ns]" << std::endl; } { std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); for (size_t i=0; i<100000000; i++) add2(a, b); std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); std::cout << "Time difference = " << std::chrono::duration_cast<std::chrono::nanoseconds> (end - begin).count() << "[ns]" << std::endl; } return 0; }
以上代碼分別執行add1
和add2
函數各一億次,計算兩者耗時,結果以下。優化
Time difference = 146[ns] Time difference = 56[ns]
在這個case裏,使用restrict可以得到2+倍的性能提高!注意使用restrict的時候,編程者必須確保不會出現pointer aliasing
, 即同一塊內存沒法經過兩個或以上的指針變量名訪問。不知足這個條件而強行指定restrict, 將會出現undefined behavior
PS: 此篇文章有感於clickhouse
近期一個與restrict有關的性能優化(https://github.com/ClickHouse/ClickHouse/pull/19946),只是由於在聚合相關的函數中加上restrict關鍵字,便能使聚合性能提高1.6倍!因此對於我輩碼農來講,多瞭解一些底層原理永遠不虧,說不定哪天你就用上了~
推薦閱讀
更多精彩內容,請掃碼關注微信公衆號:後端技術小屋。若是以爲文章對你有幫助的話,請多多分享、轉發、在看。