400 likes | 474 Views
第九章. 效能評量、程式最佳化與測試. 本章學習重點. 效能評量的方法與分析方式 最佳化方式的介紹,包含程式碼的修改、省電的策略、空間縮小的方式 測試策略的介紹. 效能評量. 即使我們在系統設計上,考量了許多細節因素,包括選擇了擁有最高運算能力的硬體,以及具有即時排程的作業系統,但是這些因素並沒有辦法保證我們的系統在執行的期間能表現出最高效能 我們希望設計系統時可以達到即時的目的,因此分析每個程式或是行程所需要的執行時間就變得很重要. 效率量測的難題. 執行時間的長短常常與資料的輸入有關 快取將會大幅影響程式的效率
E N D
第九章 效能評量、程式最佳化與測試
本章學習重點 • 效能評量的方法與分析方式 • 最佳化方式的介紹,包含程式碼的修改、省電的策略、空間縮小的方式 • 測試策略的介紹
效能評量 • 即使我們在系統設計上,考量了許多細節因素,包括選擇了擁有最高運算能力的硬體,以及具有即時排程的作業系統,但是這些因素並沒有辦法保證我們的系統在執行的期間能表現出最高效能 • 我們希望設計系統時可以達到即時的目的,因此分析每個程式或是行程所需要的執行時間就變得很重要
效率量測的難題 • 執行時間的長短常常與資料的輸入有關 • 快取將會大幅影響程式的效率 • 從低階的指令集層級來看,不同的指令集組合,會產生不同的執行時間
量測程式執行的時間 • 模擬器 (simulator) • 計時器 (timer) • 邏輯分析儀 (logic analyzer)
執行時間的種類 • 平均案例執行時間 (average-case execution time) • 最差案例執行時間 (worst-case execution time) • 最佳案例執行時間 (best-case execution time)
分析程式執行時間 • 執行時間的定義 • 執行時間 = 程式順序執行指令的個數 * 平均每個指令所執行的時間 • 找出程式順序執行指令的個數 • 量測每一條路徑所耗費的時間
範例9-1 「if else」的資料相關路徑 if (a||b) /* test 1 */ { if (c) /* test 2 */ { x = r*s+t; /* assignment 1 */ } else { y=r+s; /* assignment 2 */ } z=r+s+u; /* assignment 3 */ } else { if (c) /* test 3 */ { y=r-t; /* assignment 4 */ } }
範例9-2 迴圈的執行路徑 for (f=0; i = 0; i < N; i++) { f = f + c[i] * x[i]; }
指令時間的計算 • 指令時間量測的問題: • 不是所有的指令所耗費的時間都一樣 • 指令執行時間不獨立 • 指令執行時間可能與運算子數值相關 • 可以採用查表的方式來解決 • 程式執行分支真值表
快取影響 • 快取就是為了加速所設計,用來加速指令與資料的存取時間 • 因為快取的內容與存取的歷史相依。所以須要採用追蹤驅動 (trace-driven)與靜態/動態分析的方式來決定所造成的影響
如何產生追蹤記錄 • 硬體的方式 • 利用邏輯分析儀 • 有些CPU可以自動產生這些追蹤記錄 • 軟體的方式 • 透過PC取樣 (PC sampling) • 儀表指令 (instrumentation instructions) • 模擬 (simulation)
追蹤分析 • 得到了追蹤記錄可以: • 決定全部所花費的執行時間 • 分析快取的行為 • 評估該支程式在不同CPU上面的效率
程式最佳化 • 目的在於增進程式的效能 • 常見的最佳化方式 • 迴圈最佳化 • 快取最佳化 • 省電最佳化 • 程式空間最佳化
迴圈最佳化 • 迴圈程式需要花費最多的時間作相同的事情 • 可以採用的方式: • 程式碼調整 (code motion) • 消除引導變數 (induction variable elimination) • 減少強度 (strength reduction)
範例9-4 引導變數的消除 for (i = 0; i < N; i++) for (j = 0; j < M; j++) z[i][j] = b[i][j]; for (i = 0; i < N; i++) for (j = 0; j < M; j++) { zbinduct = i * M + j; *(zptr + zbinduct) = *(bptr + zbinduct); } zbinduct = 0; for (i = 0; i < N; i++) for (j = 0; j < M; j++) { *(zptr + zbinduct) = *(bptr + zbinduct); ++zbinduct ; }
範例9-5 降低運算強度 y = x * 2; y = x << 1;
範例9-6 反覆空間範圍的改善 for (i = 0; i < N; i++) for (j = 0; j < M; j++) a[i][j] += b[i][j] * c; for (i = j; i < j+1; i++) for (j = 0; j < M - 1; j++) a[i][j] += b[i][j] * c;
範例9-7 快取空間 for (i = j; i < j+1; i++) for (j = 0; j < M - 1; j++) a[i][j] = b[i][j] * c; 4099=1000000000011b 1024=0010000000000b 4100=1000000000100b 其中M為256,而Cache為4-way the set-associative, 且a[] 的起始位置在記憶體的第 1024 位址,而 b[] 則是在第 4099 位址 移b[]至4100,可以解決快取記憶體衝突現象
省電最佳化 • 使用較好的演算法,來取代比較複雜且耗電的運算 • 減少記憶體存取的動作,將會降低耗電 • 減少存取硬碟的動作,將可以減少一些電流的耗費 • 關閉其他不使用的周邊裝置方式,或是在平常時候,讓CPU進入睡眠或是半睡眠模式的方式,延長系統電力時間 • 適當地調整所運用的指令
程式空間最佳化 • 受限於有限的經費或是硬體容量,而盡量減少佔用的記憶體空間 • 改善方式 • 改善程式的風格 • 節省指令所佔用的空間 • 把程式壓縮起來,等到要執行的時候才將部分欲執行的程式碼解開執行
快取基礎指令解壓縮 快取基礎指令解壓縮技術
程式測試 • 在設計與實作之後,一定要經過測試的階段,來找出系統可能潛藏的臭蟲 (bug) • 測試策略可以分為兩種主要方式 • 黑箱測試 (black-box):測試人員不需要理會待測系統內部的行為,只要產生測試案例去測試,看看結果是否正確即可。 • 明箱測試 (clear-box):又稱為白箱測試 (white-box),就是依據待測系統的結構來進行測試。
明箱測試 • 為了滿足測試需求,我們必須要先瞭解控制與資料的流程。 • 同時為了執行與評估這些測試,我們也必須控制程式內的變數並且觀察出計算的結果,有時候我們還需要修改程式,以便讓程式能夠進行測試。 • 一旦新增了修改的輸入與輸出,還必須不影響原本的功能性,所以進行這樣的測試時,瞭解程式的控制與資料流程相當重要。
測試步驟 • 提供一個測試程式,包含輸入部分,專門測試我們想測試的地方 • 執行待測程式與測試程式 • 觀察輸出結果,決定是否符合測試
範例9-8 控制與觀察程式 • 需要產生兩個超過上下臨界值的數值,這時候,就需要在buff[]與c[]這兩個陣列中填入適當的數值,好讓相乘累加後的結果會超過臨界值 • 利用除錯工具 (debugger),例如gdb之類的軟體工具或是ICE之類的硬體工具 result = 0; for (i = curr, j = 0; i < N; i++, j++) result += buff[i] * c[j]; for (i =0; i < curr; i++, j++) result += buff[i] * c[j]; if (result > 100) result = 100; if (result < -100) result = -100;
範例9-9 選擇測試的路徑 • 最少執行所有的表達狀況一次 • 至少執行每個程式路徑分支一次
選擇一條路徑滿足所有的程式行為 採用圖學的方式來表達不同的路徑與選擇
迴旋複雜度 • V(G) = e – n + 2
迴旋複雜度 (續) • V(G) = P + 1 if (a==b) c=d; else { if (((a==b && (e==f) || (f==a) || (a==b)) c=d; else c=f; } V(G)=5+1=6
範例9-10 利用分支測試策略來測試狀態 • 其中一個測試組合應該要採用數值:a=0、b=3、c=2,原本預期上應該會因為 [0||(3>=2)]而出現 OK的顯示。但是因為實際上跑的結果是 [0&&(3>=2)],反而沒有出現OK 假設我們的程式原本想要寫成如下: if (a || (b >= c)) { printf(“OK\n”); } 卻不小心寫成這樣: if (a && (b >= c) { printf(“OK\n”); }
範例9-10 利用分支測試策略來測試狀態 (續) • 其中一組測試應該包含 x!= default_pointer 與 x->length == 10 的組合,這樣的話,測試時將會找到因為不是給定default_pointer,但是卻出現答案的錯誤狀況 假設我們原本想要寫的程式為: if ((x == default_pointer) && (x->length == 10)) { printf(“got the value\n”); } 卻不小心寫成這樣: if ((x = default_pointer) && (x->length == 10)) { printf(“got the value\n”); }
黑箱測試 • 不需要瞭解待測程式的相關知識,隨意產生測試案例去測試的方式。 • 如果單獨使用的話,找到所有臭蟲的機會是很低的,但是如果可以配合明箱測試一起用,也可以提供一個不錯的測試案例集合,因為除了從程式碼結構方式去作明箱測試之外,如果也可以任意輸入測試案例,也許可以找出輸入介面不合理的設計。 • 測試人員可以直接從規格定義來產生測試案例,而非從程式碼的方式來產生,也可以瞭解系統是否符合規格。
黑箱測試的特性 • 亂數測試 (random tests) 的產生 • 亂數產生的案例可能包含極大或是極小的輸入資料,或是產生設計人員根本沒有預期的資料,進而找出臭蟲 • 產生非正常的資料型態 • 例如原本都是預期輸入數字,如果此時卻輸入文字,是否會產生不正常狀況亦或是出現警告畫面,這些測試對於系統來說都是很不錯的測試方式
回歸測試 • 回歸測試 (regression tests) 是另外一種很重要的測試種類,當系統建立之初,就產生一些測試案例進行測試,等到下一版系統建立完成,再用這些測試案例去測試,看看會不會本來沒問題的系統,換了新版反而出現問題 • 除非系統規格有做更動,不然理論上新版的系統也應該可以通過上一版系統的所有測試案例才對。也可以順便追蹤舊有的臭蟲是否已經在新版作了修正
效率測試 • 需要先界定時間期限到底是多少,在大部分的情況下,我們需要找出最差案例執行時間,有些情況下,也需要找出最佳案例和平均案例執行時間。 • 可以使用硬體上的計時器或是外接邏輯分析儀的方式來進行,以便測試這些需要即時性完成的程式碼,是否都可以在限定的時間內全部達成
總結 • 在嵌入式系統專案進行到收尾的階段,如果能夠針對效率進行測量,即可以找出效率瓶頸所在,特別是我們常常需要最差案例執行時間,與平均案例執行時間作為效率參考。 • 如果能夠將專案的一些地方進行最佳化的動作,有時候小小的改變卻可以大幅增加程式執行效率。不過前提是修改的所在是否為瓶頸的地方,不然花了很大的力氣去進行最佳化的動作,卻沒有收到預期的效果 • 測試是決定專案品質很重要的地方,往往也可以從測試裡找出許多臭蟲,一般測試可以分成黑箱測試與明箱測試兩大類 • 藉由測試去驗證專案確實達到當初規格所定義的內容,才能夠算是完成測試的動作