通過正確的權衡來獲得最便捷有效的故障排除及最快速可行的優化

發表於2014-03-28

編寫一個執行得快的程式並不容易。編譯器可以幫助將一個程式轉換成執行得更加快,但權衡是轉換之後的程式與初始的程式會有所不同。在某些作了積極優 化轉換(aggressive optimization transform)的情形裡,從人類的角度看轉換後的程式與初始程式相比較已經幾乎沒有可讀的一致性了。

因此,除錯有問題的已優化程式會更加困難。這是一個問題,因為為了獲得最大化的效能優勢,大多數產品程式碼都執行程式的優化版本。

除錯已優化程式

通 常,當您嘗試通過一個偵錯程式去除錯一個高度優化過的程式,與初始程式碼相關的許多資訊已無法呈現 — 又或者即便可以顯示,也不正確。通常的情形包括除錯一個已被內聯的函式或顯示一個已被優化的變數值。這些情形經常需要人工對映彙編程式碼或程式的其他展現形 式來與初始程式碼進行比較 — 這一過程很困難或很容易出錯。

IBM XL C/C++ 編譯器總是可以嚴格限制優化或為應用程式提供除錯資訊,來使得對程式進行除錯更加簡單。在跟隨 z/OS V2R1 發行版釋出的 z/OS XL C/C++ 編譯器中,一組主要的偵錯程式功能增強是在普通優化級別及內聯機制上,這使得除錯已優化的程式變得更加容易。

通過引入偵錯程式級別,編譯器可以提供關於初始程式各個方面準確和確切的資訊 — 例如,引數的值和分支點上允許的斷點。通過內聯除錯方面的增強,編譯器提供了各個變數在函式或過程的內聯例項中的區域性數值方面的資訊。

這些除錯增強功能使得程式設計更加容易快捷,並減少維護成本;生成的應用程式不僅可以被優化,而且仍然可以被除錯。

遍歷程式碼

在原始碼級別,您可以描述程式應當如何執行,這也是您最熟悉的程式碼級別。但 為了更好地利用硬體 — 例如嘗試填充一個指令快取來減少檢索程式碼的延遲 — 優化可能改動了程式碼。這樣做之後,指令的順序在原始碼級別和機器實際執行的可執行程式碼級別上,實際上已經不同。因此,當您除錯可執行程式碼時,您無需與源代 碼進行一對一的比對。

除錯與優化選擇

在特定的原始碼行放置斷點(breakpoint)並不會導致偵錯程式停止可執行程式碼,在偵錯程式中通過步進的方式執行程式也無法使之停止在下一行程式碼行上。保持與原始碼次序一致的執行順序會限制編譯器所能執行的優化,只能得到一個執行緩慢的應用程式。

不 過,有一個折中的方式。通過折衷某些級別的“原始碼-可執行程式碼的直接對映”(source-code-to-execution-code direct mapping),編譯器可以優化直接對映的原始碼行之間對應的執行點,並保持在重要的原始碼行上的對映,這樣來允許在那些執行點上進行除錯。

除錯級別支援與資訊

除錯級別支援(debug level support)提供了除錯級別的範圍,或者關於偵錯程式所提供除錯資訊正確性的最小範圍的約定。例如,在高階別,級別 9,所有可執行語句都可以在偵錯程式中停止,所有變數的值都可以被改變並使改變反映在執行程式的後續執行中。另一方面,除錯級別 1 則只提供行號表資訊,並不保證也不約定可以在任意特定原始碼行停止的能力。

其他級別在這兩種極限之間的標誌性程式碼事件上提供停止點,例如分支點(if 語句、函式呼叫、迴圈入口),可以提供除錯大部分應用程式所需要的主要資訊。此外,對於檢視變數值的常見情形,範圍則從完全不保證能看到準確值的情形變化到可以看到函式引數及所有變數值的改變。

您可以從表 1 的總結看到所有重要的除錯級別,以及這些級別所提供的相關資訊:

表 1:除錯級別影響範圍
除錯級別(Debug level) 啟用斷點(Breakpoint)的原始碼行 變數影響(Variable effect)
1 – 生成無保證的行號表,行號只與未進行優化的程式原始碼行相關 – 無變數資訊
2 – 生成無保證的行號表,行號只與已進行優化的程式原始碼行相關(與偵錯程式級別 1 相同) – 變數的資訊已被生成,但不保證正確性
3 – 生成無保證的行號表,行號只與已進行優化的程式原始碼行相關(與偵錯程式級別 1 相同) – 函式引數在專門提供給 KPLINK 的記憶體中可見
5 – 有 if 語句、函式呼叫、迴圈以及函式的第一行可執行語句都可以設定停點
– 行號表只列出有 if 語句、函式呼叫、迴圈以及函式的第一行可執行語句所在的行
– 變數只在原始碼行的列中所給出的點上可見,而且僅在這些點上是正確的
8 – 每一句可執行語句
– 行號表只列出每一個可執行語句行
– 變數只在原始碼行列中給出的點上是正確的
9 – 每一句可執行語句
– 行號表只列出每一個可執行語句行
– 變數只在原始碼行列中給出的點上是正確的且可以修改

註解:
沒有在表 1 中列出的偵錯程式級別其表現與表中已列出的上一級相同 — 例如,級別 4 提供了與級別 3 完全相同的資訊。這些未列出的級別在未來的版本中或許會有不同的功能性。

除錯行內函數

當編譯器優化程式時,它通常行內函數或過程來減少 一個顯式函式呼叫的開銷。這些內聯過程包括例如前言(prolog)和結尾(epilog)程式碼,它們是除函式程式碼本身的以外需要執行的程式碼。行內函數通 常擁有它們獨有的函式引數及區域性變數,如果函式尚未內聯的話,可以通過直接關聯初始程式碼來進行除錯。在內聯了函式之後,通常二義性(例如變數在呼叫的源函 數中擁有同名變數)會使除錯更加困難或幾乎不可能,因為這樣做的話,無法再對這一函式呼叫設定斷點。

行內函數或過程的方式

一個函式或過程可以用多種方式內聯。方式包括使用內聯選項(本身便允許進行較廣範圍的調優)、優化選項、C 和 C++ 中的 inline 關鍵字,又或者通過使用內聯 #pragmas。如果函式通過這些方式中的任何一種內聯,除錯這些函式就會出現問題。這是因為函式本身可能並不存在,又或者函式存在但被執行的函式無“例項”(因為它被內聯了)。

連結效應(Linkage effects)

此 外,XPLINK 連結規範允許引數可以傳遞到暫存器中,而不是傳遞到傳統多重虛擬儲存(Multiple Virtual Storage,MVS)連結對應的記憶體裡。這意味著期望看到和修改函式引數的偵錯程式會去修改記憶體位置裡的引數(如果 XPLINK STOREARGS 子選項被指定了)。然而這一動作對程式本身毫無作用,因為周邊程式碼使用的引數是暫存器副本。

採用除錯級別支援

使 用除錯級別支援的級別 2 及以上級別,您可以除錯行內函數引數及區域性變數資料,而無需考慮函式所使用的內聯方式。例如,使用除錯級別 8,您可以檢視行內函數每一個語句的區域性變數。在級別 9,您設定可以改變變數的值,並影響程式。函式引數可以被修改,而且改變會反映到正在執行的程式中。

行內函數的常見例子包括物件導向程式設計常用的 getter 及 setter 方法。這些函式,一旦內聯,在足夠高的除錯級別上可以使它們的引數被偵錯程式在執行時改變。例如,setter 函式可以改變引數值來允許設定一個不同的值,不同於初始原始碼所描述的值。

指定和使用偵錯程式級別

在本文中所描述的偵錯程式級別是 z/OS V2.1 XL C/C++ 編譯器中的新引入的,所以當前對這些編譯器級別的需求還很少。雖然,新的偵錯程式級別特性避免了與已有構建版本的相容性問題。

在 Unix® System Services(USS)中已有的 –g 選項,沒有改變其行為。不建議使用的 TEST 選項仍然提供 ISD 除錯資訊,並且,因為是不建議使用選項,並沒有在這次的新級別支援中得到增強。DEBUG 選項則通過新偵錯程式級別得到了增強,並作為 LEVEL 子選項的新值。已有的預設值並沒有被改變,預設的 LEVEL 子選項的含義也沒有改變。偵錯程式級別 0 仍然表示其之前的含義,並且對於非優化編譯來說與新的級別 9 等同,對於優化編譯來說等同於級別 2。

簡化除錯規範

如果是在 USS 中使用偵錯程式級別,已經新增了新的標誌(flag)來使除錯規範變得更簡單。–g 標誌現在可以在它的後邊用可選的數字來代表偵錯程式級別。

註解:
–g flag 新的數字版本允許優化使用相應的除錯特性,而 –g 標誌 沒有 數字跟隨其後的話,則沿用舊的行為,即強制進行沒有優化來允許完全的除錯特性。例如,表 2 顯示了等同的命令列,來啟用在每一個可執行語句實現停止及檢視(並不需要改變)變數的值:

表 2:等同的偵錯程式級別規範

無需新的庫及資料集

偵錯程式級別不需要任何新的庫或資料集被包括在構建中或用於執行程式。當然,偵錯程式必須能夠解析編譯器所給的除錯資料,但由於所使用的除錯資訊格式是使用開放的 DWARF 格式,這一資訊很容易解讀。

對 於不同型別的程式、程式設計組成和優化級別來說,通過更高的除錯資訊級別來進行效能權衡得到的結果不盡相同,因此並不存在哪一個偵錯程式級別是最好的萬能規則。 通過使用這一更大範圍的除錯級別,您現在比以前擁有更多的選擇來進行權衡,因此您可以調整優化及偵錯程式級別來獲得您程式的最佳結果。可以不斷地試驗直到您 找到最佳的組合為止。

結論

除錯一個程式和使它執行得更快之間通常是衝突的。通常只能選擇其中之一:要麼是一個可以除錯但執行速度不理想的程式,要麼是一個執行速度很快但在除錯方面非常受限而且很難與初始原始碼相關聯的程式。

通 過採用不同級別的除錯資訊生成過程和可執行程式碼修改過程來實現與初始原始碼的緊密聯絡,使得一組在“速度快但難以除錯的程式”和“速度慢但易於除錯的程 序”之間的選擇範圍得以實現。基於分支和變數值查詢的首要除錯領域已經相比以前提供了更高優化級別,因此您可以實現既可以易於除錯又可以快速執行的產品代 碼。

除錯行內函數的能力同樣為除錯已優化過程式的可能性帶來了顯著的提升。這一能力在物件導向的 C++ 程式碼中非常有益。行內函數可以使它們的引數被顯示甚至被修改,而採用足夠高的除錯級別,區域性變數同樣也可以被除錯。

新的除錯權衡帶來了新的子選項值。因此,您可以根據需要簡單通過重編譯來加入新的除錯支援,而先前的構建版本和程式可以延續舊的模樣。

相關文章