03.05 提升C++代碼性能:請善用final指示符

final指示符簡介

C++中的final指示符可以將一個類或者虛函數標記為不可繼承或重寫。讓我們先看看如下的代碼:

提升C++代碼性能:請善用final指示符

如果我們嘗試創建一個新類並繼承自[derived],則我們會得到一個編譯錯誤,如下圖所示:

提升C++代碼性能:請善用final指示符

final指示符向類的客戶明確傳遞了這樣的信息:這個類不應該用來被繼承,並且使用編譯器來保證這一點。同時,使用這個標識符可以使用去虛擬化(Devirtualization)來優化代碼的性能。

去虛擬化(Devirtualization)

虛函數需要通過虛函數表(vtable)進行間接調用,相對於直接調用來說,間接調用更加昂貴,因為它會受到分支預測和指令緩存的影響,同時,間接調用還阻止了編譯器對代碼的更進一步的優化,例如,例如,通過將代碼內聯來提升運行時性能。

去虛擬化是一項編譯期優化措施,它可以在編譯期解析出虛函數調用,而不是在運行時進行決斷。這項優化解決了上面談到的問題,因此它可以顯著的提升代碼的性能,尤其是代碼裡含有大量的虛函數調用的時候。

下面是一個去虛擬化的例子代碼:

提升C++代碼性能:請善用final指示符

在上面的代碼中,即使[dog::speak]是一個虛函數,[main]函數的唯一輸出只可能是”woof”,如果你仔細的查看編譯器輸出,你會發現,MSVC, GCC和Clang這些編譯器都會識別到這一點並將[dog::speak]的定義內聯到[main]函數中,這樣就避免了運行時的間接調用,從而提升了代碼性能。

final指示符帶來的性能提升

通過在代碼中使用final指示符,編譯器可以獲得更多的機會執行去虛擬化。因為如果代碼中使用到了final,就表示類的虛函數可以在編譯期進行解析,從而使得編譯器可以進行去虛擬化。我們還是看之前的例子代碼:

提升C++代碼性能:請善用final指示符

考察一下代碼:

提升C++代碼性能:請善用final指示符

因為[derived]被標識為final,所以編譯器知道它不可被繼承。這意味著對虛函數[f]的調用只會調用到[derived::f]上,所以這個調用可以在編譯期進行解析。當[derived]或者[derived::f]被標識為final時,我們可以在MSVC的編譯輸出中看到如下的證據:

提升C++代碼性能:請善用final指示符

你可以看到,[derived::f]被內聯到了[call_f]的定義中。如果我們將final標識符從代碼中去掉,則我們會觀察到如下的彙編代碼:

提升C++代碼性能:請善用final指示符

以上代碼將會加載對象[d]的虛函數表,然後通過保存在虛表相對位置的函數指針對[derived::f]發起一次間接調用。

[load]和[jump]指令的成本可能看起來不是很多,因為它就是兩條指令而已。但是這會帶來錯誤的分支預測或者指令緩存未命中,從而帶來一次[pipeline stall]。而且,如果在[call_f]中包含更多的代碼或者更多的函數調用它的話,編譯器將會看到代碼調用樹的全景並進行額外的分析,從而有可能對代碼進行更為激進的優化。

總結

通過將你的類或者虛函數聲明為final,我們可以提供給編譯器更多的優化可能性,使得編譯器可以在編譯期就對虛函數調用進行解析,從而避免了運行時的間接調用成本。

那麼,今天在你的工程中試試吧,看看代碼是不是跑的更加歡快了?


分享到:


相關文章: