【第166期 July 5, 2011】
 

研發新視界

程式碼效能剖析

作者/江彥伯

[發表日期:2011/7/1]



簡介

程式效能一直是系統開發人員所重視的問題,程式人人會寫,但是要寫出高效能的好程式卻不簡單,除了要不斷吸收新的技術,還要多方面的嘗試才能夠了解在那些情況下該用那些方法來解決問題。郭台銘語錄中收錄了一句話:「魔鬼都藏在細節中」,意思是說:最常被忽略的小事,有可能是導致嚴重失敗的原因。系統的效能問題正是如此,如果程式開發人員不重視程式中的每個小細節,當各個效能有問題的程式合成為系統時,整個系統就會有嚴重的效能問題。

微軟的開發工具Visual Studio 2010提供了程式碼剖析的功能,開發人員可以利用這個功能找出程式執行期間造成程式執行效率不佳的程式碼進行改善,也可以針對不同方法的效能進行比較及分析。接下來本文將會介紹如何使用Visual Studio 2010效能分析工具,以及如何使用效能分析工具找出程式執行效能上的瓶頸點,然後會利用這個工具針對開發過程中常遇到的幾個效能問題使用不同的方法來進行效能比較及分析。

程式碼效能分析

Visual Studio 2010 Premium與Visual Studio 2010 Ultimate提供了程式碼剖析的工具,開發人員可以利用這個工具對自己所寫的程式碼進行分析,以便找出程式執行效能上的瓶頸,然後針對瓶頸點作不同的調整,並且對不同方式的調整作比較,最後找出最佳的調整方式。

一、開啟工具列上的〔分析〕>〔啟動效能精靈〕來建立效能工作階段。


《圖一》啟動效能精靈


二、在〔指定程式碼剖析方法〕設定要使用何種方式和何種指標來做為效能評估的方式。方式有兩種:

1.取樣:定期中斷應用程式然後收集正在執行的函式並遞增此函式的取樣計數,其優點為分析期間造成的系統負荷較低,缺點是只能取得有取樣到的函式資料,若程式執行時間過短,將有可能為取樣到而不會出現在堆疊上。

2.檢測:在每個函式開始與結束都進行資料的收集,好處是可以精準計算出每個函式所耗用的資源,但缺點是檢測的額外負荷大於取樣,執行速度將會非常緩慢。

而效能評估的指標大多以CPU資源的佔用和記憶體的配置為主。


《圖二》指定程式碼剖析方法


三、選擇要被進行分析的程式,選擇完畢後就算完成了基本的設定,就能夠在〔效能總管〕上看到剛才設定完成的〔效能工作階段〕。


《圖三》效能總管


四、點選〔啟動分析〕後程式會自動啟動,此時便可以開始操作程式,而在操作的過程中程式碼剖析工具會去紀錄程式使用系統資源的情形,當操作完成關閉程式後分析報告會自動產生,並顯示下圖的報告摘要。


《圖四》分析報告摘要


從報告摘要中可以看出:

一、程式執行期間不同時間點的CPU使用率
二、最忙碌路徑下的函式,以及該函式的內含樣本比例及專有樣本比例,
  內含樣本:目標函式執行期間的總樣本數,包含目標函式呼叫的子函式
  專有樣本:目標函式的總樣本數扣除目標函式呼叫的子函式樣本
三、執行最多個別工作的函式則是顯示專有樣本比例最高的函式。
四、在CPU使用圖上選取範圍然後點選〔依選取範圍篩選〕就可以篩選出特定時間區段內的樣本分布資料,並且將〔最忙碌路徑〕及〔執行最多個別工作的函式〕更新成為選取時間區段內的統計資料。

此外點選〔最忙碌路徑〕及〔執行最多個別工作的函式〕中的函式連結還可以直接顯示〔函式詳細資料〕


《圖五》函式詳細資料


〔函式詳細資料〕顯示了
1.呼叫這個函式的函式及內含樣本比例
2.這個函式所呼叫的子函式及各子函式內含樣本比例
3.各子函式的內含樣本比例所對應到的程式碼,程式碼標記紅色的部分極有可能是造成程式效能瓶頸的所在

此外,點選〔呼叫這個函式的函式〕或〔這個函式所呼叫的函式〕中的函式也可以檢視其〔函式詳細資料〕。

〔呼叫樹狀圖〕會顯示所有函式的樣本統計,並且依照呼叫的方式以樹狀結構呈現


《圖六》呼叫樹狀圖


程式開發人員可以利用Visual Studio 2010的程式碼效能分析工具提升程式碼效能

1.查看程式執行操作過程中呼叫到的函式所包含的樣本數並找出執行期間包含樣本比例較高的函式,而包含樣本比例較高的函式極有可能是程式效能的瓶頸點

2.針對這些函式的程式碼作修正,修正過後再次〔啟動分析〕

3.取得更新過後的分析報告,然後再跟修正前的分析報告作比較,就可以得出效能較佳的程式撰寫方式


《圖七》比較效能報告


從報告的比較結果當中可以看出各函式的內含樣本比例的前後變化,而我們所要關心的是先前造成程式效能瓶頸的函式在修改過程式碼後執行結果的內含樣本比例是否有下降。


《圖八》報告比較結果


常見效能問題

程式效能的低落往往來自開發人員不良的程式撰寫觀念,不知道什麼樣的情況下該使用什麼樣的方法才會使程式有較佳的效能,不好的使用方式遍布於系統當中。所以開發人員在開發的過程當中就該時時注意程式效能的問題,而不是到了整個系統架構完成才發現效能不如預期,這個時候要在進行修改則需要耗費更多的心力。所以本文將利用此工具來對一些C#開發人員常見的一些問題進行程式碼剖析,藉此證明程式在那些情況下使用什麼樣的方法會有較好的執行效能。

一、字串連接:

大部分的開發人員都認為在做大量的字串連接時使用StringBuilder會比string相加要來得快,而且微軟網站的使用效能規則中也提到必須使用StringBuilder進行串聯,是因為string相加每次都需要重新配置記憶體空間造成效能低落,所以做字串連接的時候使用StringBuilder會有比較好的效能。但是在某些情況下,使用StringBuilder來連接字串不見得會比使用string相加還要來的有效率,以下面兩段程式碼為例。

程式範例一:

《圖九》


程式範例二:

《圖十》


使用迴圈對這兩段範例程式碼執行1000000次進行程式碼效能分析

《圖十一》


會發現使用StringBuilder的函式的內含樣本比例高達90.5%,而使用string相加的函式的內含樣本比例卻指有1.1%,使用StringBuilder的效能遠不及使用string相加。但是如果將以上的兩個程式範例改成

程式範例一:

《圖十二》


程式範例二:

《圖十三》


同樣對這兩段程式碼進行效能分析

《圖十四》


發現這次的效能分析中,使用StringBuilder的函式內含樣本比例卻只有29%,而使用string相加的函式卻有39.4%。明顯看出使用StringBuilder有較佳的效能,其原理為string相加進行一連串的靜態資料串接在編譯的時候會自動連接,所以在執行的時候不需進行太多額外的記憶體配置,而StringBuilder優於string的地方在於「動態資料」的連接。而經過實測,當動態資料約大等於五筆時,使用StringBuilder的效能才會優於string。

二、迴圈的使用:

過去開發人員普遍認為使用foreach的效能較for差,覺得foreach在搜尋各資料列的特定行資料時必須整個掃過該資料列的所有資料,所以在此利用程式碼效能剖析工具對這兩種不同的迴圈用法進行效能分析。建立一個10行100000列的DataTable,然後對各列的特定行資料做修改。

程式範例一:

《圖十五》


程式範例二:

《圖十六》


效能分析:

《圖十七》


從分析結果當中看到使用foreach迴圈的函數內含樣本比例為17.2%,而使用for迴圈的函數內含樣本比例為28.9%,由此可見使用foreach迴圈的效能是優於使用for迴圈的。此外,筆者有再做額外的測試,試著將資料行增加至100行,雖然兩者效能的差距有變小,但是使用foreach函式還是比使用for函式的效能好,在更多資料行的情況下也許使用for函式的效能會超越使用foreach的函式,但是一般的情況下開發人員要使用到超過100行的資料並不常見。

三、資料庫讀取:

開發人員在進行資料庫存取的時候通常會使用較方便的DataAdapter,但是往往忽略還有DataReader可以使用。然而,使用不同的資料庫讀取方法,效能表現上也有所不同,在此分別使用DataReader和DataAdapter讀取資料庫的同一行資料進行程式碼效能剖析:

程式碼範例一:

《圖十八》


程式碼範例二:

《圖十九》


進行程式碼效能分析後的結果

《圖二十》


從結果可以看出使用DataReader的內含樣本比例只有25.4%,但是使用DataAdapter的內含樣本比例卻高達46.1%,可見DataReader在讀取資料上的效能是優於DataAdapter的。主要原因是DataReader會將查詢結果儲存於網路緩衝區中然後再用Read()讀取查詢的資料來加快讀取的速度,而且DataReader一次只將一個資料列儲存到記憶體中,進而減少系統的負荷。

總結

程式開發人員可以利用Visual Studio 2010的效能分析工具統計出各段程式碼大約的執行時間比率,可以輕鬆找到造成系統效能低落的瓶頸點,針對瓶頸點的程式碼進行修改然後再次進行分析,在對修改前後的分析報告做比較,進而找出較好的解決方法。

利用程式碼效能剖析工具分析本文所舉的三個常見的效能問題,可以讓大家了解到一些普遍性的效能問題,在往後的開發過程當中避免使用到效能不佳的程式撰寫方式。但是,在實際的開發過程中開發人員所遇到的問題可能不同於範例,也有可能更複雜,相同的用法在不同的情況下效能可能也會有所差異,本文所舉的字串連接就是最好的一個例子。所以,在開發的過程中開發人員就該時常針對不同的情況使用不同的方法進行程式碼效能剖析,才能學習到在各種不同的情況下該使用哪種方法才會有較好的效能,而這才是程式碼效能分析工具的最大功用。

參考資料

1.效能分析的初級開發人員指南

2.ataAdapter 和 DataReader (ADO.NET)

3.DA0001:使用 StringBuilder 進行串連