程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 優化C++代碼(3)常量合並

優化C++代碼(3)常量合並

編輯:C++入門知識

這篇文章講的是常量合並,這是VC++編譯器最簡單的優化之一。  這種優化,是指編譯器在編譯時(編譯期間)直接計算出表達式的結果,在生成的代碼中直接用計算結果替換表達式。 這樣就避免了程序在運行時執行這些計算花費的成本。

下面是一個例子 APP.cpp文件中的 main 函數:

  1. int main() { return 7 + 8; } 

首先,關於這篇文章的一些須知:

如果你想要繼續,請看下說明。實際上,你只需要從Visual Studio 列表裡選擇一個正確的變體。

注意:如果你正在使用Visual Studio Express上的免費編譯器,它僅僅只能運行在x86上,但是也會順利生成x64的代碼。對這個實驗同樣有用。)

我們可以通過命令 CL /FA App.cpp來構建示例程序。用/FA開關創建一個輸出文件,用來保存編譯器生成的匯編代碼,可以輸入type App.asm來顯示:

  1. PUBLIC  main 
  2. _TEXT   SEGMENT 
  3. main    PROC 
  4.         mov     eax, 15 
  5.         ret     0 
  6. main    ENDP 
  7. _TEXT   ENDS 
  8. END 

有趣的是這條指令 move ax,15—-僅僅將15賦值給寄存器EAX(根據x64調用標准的定義,x64函數將會設置一個int值,作為函數的結果,並返回給調用者)。編譯器運行期間並沒有發出 7加8的指令。就像下面這樣:

  1. PUBLIC  main 
  2. _TEXT   SEGMENT 
  3. main    PROC 
  4.         mov     eax, 7 
  5.         add     eax, 8 
  6.         ret     0 
  7. main    ENDP 
  8. _TEXT   ENDS 
  9. END 

注意看了,這兩段代碼的最後一條指令,ret 0,是指將控制權返回給調用者,並從棧裡彈出0個字節。不要被誤導認為是返回數值0給調用者!)

我猜到,你可能在想:這很好啊,但是哪個白癡會想到在代碼裡寫 7+8 這樣的運算?的確,你是對的,但是編譯器會把這樣的結構看成是有副作用的宏。看了下面的例子,你就會明白常量合並是一個很有用的優化方法:

  1. #define SECS_PER_MINUTE  60 
  2. #define MINUTES_PER_HOUR 60 
  3. #define HOURS_PER_DAY    24 
  4.   
  5. enum Event { Started, Stopped, LostData, ParityError }; 
  6.   
  7. struct { 
  8.     int        clock_time; 
  9.     enum Event ev; 
  10.     char*      reason; 
  11. }   Record; 
  12.   
  13. int main() { 
  14.     const int table_size = SECS_PER_MINUTE * MINUTES_PER_HOUR * HOURS_PER_DAY * sizeof Record; 
  15.     // rest of program 

我們要創建一個足夠大的表保存每一秒的記錄,所以table_size就是表的大小,用字節表示。很容易查看變量table_size的匯編指令:

  1. mov     DWORD PTR table_size$[rsp], 1382400     ; 00151800H 

這兒沒有乘法指令,60*60*24*16=1382400 是在編譯時計算的。

事實上,我們窺探下編譯器的內部,會發現這種常量合並的運算非常簡單,它是由前端來執行的。它並不需要後端優化器笨重的提升能力。所以它總是存在的。不管你是開啟優化使用 /O2)或者關閉優化/Od)都沒什麼區別—–該優化總是自動執行的。

不管表達式有多復雜,我們都能在編譯期間進行常量合並嗎?—事實上,前端可以處理任意的常量算術表達式甚至包括上面提到的sizeof,只要它們在編譯時能被計算出來)和運算符+ - * / % << >> ++ 和 –)。你甚至可以使用布爾值,邏輯運算符 和條件運算符if AND ?:。

有沒有常量合並需要後端優化器的時候呢?當然有,看下面的例子:

  1. int bump(int n) { return n + 1; }  
  2.    
  3. int main() { return 3 + bump(6); }  

輸入命令cl /FA /Od App.cpp,會得到信息:不能優化,謝謝!,輸入 App.asm,我們會得到:

  1. mov     ecx, 6 
  2. call    ?bump@@YAHH@Z                           ; bump 
  3. add     eax, 3 

正如我們所預料的: ECX會保存第一個參數6,根據x64調用約定,然後調用bump函數,結果返回給EAX,然後EAX再加3。

我們來看看如果我們使用 cl /FA /O2 App.cpp 來進行優化,會發生什麼。

  1. mov eax,10 

後端優化器已經識別到bump函數很小,可以包含到調用者裡我們在後面的章節將會講到這種優化方法,叫做內聯函數)。它在編譯時就能夠估算出整個表達式的值,最後只剩下一條單指令。很神奇,對吧?

譯文鏈接:http://blog.jobbole.com/47191/

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved