1. 程式人生 > >.NET/C# 在程式碼中測量程式碼執行耗時的建議(比較系統性能計數器和系統時間)

.NET/C# 在程式碼中測量程式碼執行耗時的建議(比較系統性能計數器和系統時間)

我們有很多種方法評估一個方法的執行耗時,比如使用效能分析工具,使用基準效能測試。不過傳統的在程式碼中編寫計時的方式依然有效,因為它可以生產環境或使用者端得到真實環境下的執行耗時。

如果你希望在 .NET/C# 程式碼中編寫計時,那麼閱讀本文可以獲得一些建議。閱讀本文也可以瞭解到 QueryPerformanceCounterGet­System­Time­As­File­Time 等方法的差異。


本文內容

基本的計時

計時一般採用下面這種方式,在方法執行之前獲取一次時間,在方法結束之後再取得一次時間。

// 在方法開始之前。
Foo();
// 在方法執行之後。

這樣,前後兩次獲取的時間差即為方法 Foo 的執行耗時。

這裡我不會提到效能測試工具或者基準效能測試這些方法,因為這些測試程式碼不會運行於使用者端。你可以閱讀以下部落格獲得這兩者的使用:

結論:使用什麼方法計時

先說結論:System.Diagnostics 名稱空間下有一個 Stopwatch 類。如果你要為你方法的執行時間進行統計,那麼就使用這個類。

Stopwatch 類有一些靜態屬性、也有一些例項方法和例項屬性。此型別的時間統計是按照高效能和高精度的要求來做的,於是你可以用它獲得高精度的計時效果。不過,如果你對效能要求近乎苛刻,例如你的方法會被數百萬次或更高頻地執行,那麼就需要開始斟酌如何呼叫裡面的屬性了。

簡單的使用如下面這樣:

var
watch = Stopwatch.StartNew(); Foo(); watch.Stop(); var elapsed = watch.Elapsed;

當然,你也可以直接使用 Stopwatch 的建構函式,new 出來之後再 Start,不過 StartNew 靜態方法可以將兩句合併為一句。

各種計時 API 及其比較

計時還有很多的方法,你可以針對不同需求場景使用不同的方法。不過,如果你根本沒有了解過其他方法的話,那麼建議直接使用上面的 Stopwatch,不要想太多。

現在,我們看看 Windows 下的計時還有哪些 API:

  • 基於 QPC 的高精度 API
    • Query­Performance­Counter
    • Query­Performance­Frequency
  • 基於系統時間的非高精度 API
    • Get­Tick­Count, Get­Tick­Count64
    • Get­Message­Time
    • Get­System­Time, Get­Local­Time, Get­System­Time­As­File­Time
    • Query­Interrupt­Time, Query­Unbiased­Interrupt­Time
  • 基於 QPC 和系統時間的 API
    • Get­System­Time­Precise­As­File­Time
    • Query­Interrupt­Time­Precise, Query­Unbiased­Interrupt­Time­Precise

基於系統性能計數器(QPC)的 API

QueryPerformanceCounter,微軟文件中把它稱之為 QPC。

一般情況下使用的 QueryPerformanceCounter,核心驅動開發者使用的 KeQueryPerformanceCounter 和 .NET 開發者使用的 System.Diagnostics.Stopwatch 都是基於 QPC 的 API。

QPC 是通過計算機上獨立執行的高精度硬體計時模組來獲得時間戳的。這意味著,使用此 API 獲得的時間戳是本機時間戳,不包含任何時區等資訊。

由於 QPC 的高精度特性,所以非常適合在單個裝置上測量一個小段時間的時間間隔。而這也符合我們本文一開始說到的方法執行耗時測量需求。

QueryPerformanceCounter 得到的值是 Ticks,單位是 100 ns。

1 tick  = 100 ns
1 us    = 1000 ns
1 ms    = 1000 us
1 s     = 1000 ms

基於系統時間的 API

如果你的需求不止是測量獲取一個時間間隔,而是需要一個長期儲存的時間,或者需要將時間與其他裝置進行通訊,那麼基於單臺裝置的 QPC 就不符合要求了。

GetSystemTimeAsFileTime 可以用來獲取系統時鐘時間。這個時間就是基於系統時鐘的,所以如果你的時間戳是用來通訊的,那麼就很有用。當然,如果要在裝置之間進行與時間資訊相關的同步,還可能需要使用 NTP(Network Time Protocol)先同步時間。

DateTime.Now 獲取時間的方法就是這個:

[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern long GetSystemTimeAsFileTime();

這裡有一些比較有趣的說法,基於系統時間的 API 也會說成是獲取高精度時間,那麼跟 QPC 有什麼不同呢?

這裡我只能拿英文來說話了。來自微軟的 Raymond Chen 在它的 The Old New Thing 一書中說,基於系統時間的 API 獲取的時間戳精度用的是 “所謂的 Precise”,但實際上應該稱之為 “Accurate”,而 QPC 才能稱之為實質上的 “Precise”。糾結起來就是 QPC 比基於系統時間的 API 得到的時間戳精度更高。

基於 QPC 和系統時間的 API

Get­System­Time­Precise­As­File­Time 這些 API 既可以獲得 QPC 的高精度,又與系統時鐘相關,於是你可以使用這些 API 同時獲得以上測量的好處。當然,這以效能成本為代價的。


參考資料