AnT..
82
您可以看到,在從性能角度來看實際上很重要的情況下,例如在一個周期中多次重復調用該函數,性能可能根本不同.
這對于人們來說可能聽起來很奇怪,他們習慣于將C代碼視為由抽象的C機器執行的,其機器語言與C語言本身密切相關.在這種情況下,"默認情況下"對函數的間接調用確實比直接調用慢,因為它正式涉及額外的內存訪問以確定調用的目標.
但是,在現實生活中,代碼由真實機器執行,并由優化編譯器編譯,該編譯器非常了解底層機器架構,這有助于它為該特定機器生成最佳代碼.在許多平臺上,可能會發現從循環執行函數調用的最有效方法實際上會導致直接和間接調用的相同代碼,從而導致兩者的相同性能.
例如,考慮x86平臺.如果我們"直接"將直接和間接調用轉換為機器代碼,我們可能最終得到類似的東西
// Direct call
do-it-many-times
call 0x12345678
// Indirect call
do-it-many-times
call dword ptr [0x67890ABC]
前者在機器指令中使用立即操作數,并且通常比后者更快,后者必須從某個獨立的存儲器位置讀取數據.
在這一點上,讓我們記住,x86架構實際上還有一種方法可以為call指令提供操作數.它在寄存器中提供目標地址.關于這種格式的一個非常重要的事情是它通常比上述兩種都快.這對我們意味著什么?這意味著一個好的優化編譯器必須并將利用這一事實.為了實現上述循環,編譯器將嘗試在兩種情況下通過寄存器使用調用.如果成功,最終代碼可能如下所示
// Direct call
mov eax, 0x12345678
do-it-many-times
call eax
// Indirect call
mov eax, dword ptr [0x67890ABC]
do-it-many-times
call eax
注意,現在重要的部分 - 循環體中的實際調用 - 在兩種情況下都完全相同.毋庸置疑,性能幾乎完全相同.
有人甚至說,但奇怪的是聽起來,這個平臺上直接調用(與立即數的調用call)是慢比只要間接調用中提供的操作數的間接調用寄存器(相存儲在內存中).
當然,在一般情況下,整個事情并不那么容易.編譯器必須處理有限的寄存器可用性,別名問題等.但是像你的例子中那樣簡單的情況(甚至在更復雜的情況下),上述優化將由一個好的編譯器執行并將完全消除循環直接調用和循環間接調用之間的性能差異.當調用虛函數時,這種優化在C++中特別有效,因為在典型的實現中,所涉及的指針完全由編譯器控制,使其充分了解別名圖片和其他相關內容.
當然,總有一個問題是你的編譯器是否足夠智能以優化這樣的事情......