野人獻曝 - 極簡風格 .NET Stopwatch 計時法
6 | 10,345 |
在 .NET 要測量執行時間,Stopwatch 是最簡單直覺的做法,像這樣:
排版顯示純文字
Stopwatch sw = new Stopwatch();
sw.Start();
//...執行要測試的動作
sw.Stop();
//將測得秒數輸出到Console、Debug或Log檔
Console.WriteLine($"Time={sw.ElapsedMilliseconds:n0}ms");
說起來不複雜,但一但測量對象變多,專案將充斥大量 Stopwatch 建立、開始、結束以及記錄時間的程式碼。遇到大範圍要計時,內部也要分段計時的需求,還得宣告多個 Stopwatch 物件並注意命名不能重複。再則,若想透過 config 統一啟用或停用所有計時功能,在每一段計時程式都要多加 if,很醜。
基於上述考量,我會寫成共用函式或程式庫集中程式碼,但用起來還是有點囉嗦,直到最近我想到一個好點子,將計時函式寫成實做 IDisposable 的專用物件,建構時建立 Stopwatch 並 Start() 開始計時,在 Dispose() 時 Stop() 並輸出計時結果:
排版顯示純文字
/// <inheritdoc />
/// <summary>
/// 執行時間測量範圍(自動使用Stopwatch計時並寫Log)
/// </summary>
public class TimeMeasureScope : IDisposable
{
private readonly Stopwatch stopwatch = new Stopwatch();
private readonly string _title;
public static bool Disabled = false;
/// <summary>
/// 建構式
/// </summary>
/// <param name="title">範圍標題</param>
public TimeMeasureScope(string title)
{
if (Disabled) return;
_title = title;
stopwatch.Start();
}
/// <inheritdoc />
public void Dispose()
{
if (Disabled) return;
stopwatch.Stop();
//TODO: 實務上可將效能數據寫入Log檔
Console.WriteLine(
$"{_title}|{stopwatch.ElapsedMilliseconds:n0}ms");
}
}
如此,使用 using 關鍵字可控制 TimeMeasureScope 生命週期以及計時起點與終點,從 using 開始大括號「{」開始計時,遇到結束大括號「}」截止並輸出計時結果。這樣子,只需將要測量的程式碼片段用 using (var scope = new TimeMeasureScope()) 包起來就能自動計時,而且支援巢狀套用,例如以下這個無聊範例:
唯一的副作用是 using {} 會改變範圍內宣告變數的有效範圍,無法供外部叫用,部分變數可能需調整宣告位置,但問題不大。
用起來很簡單吧,實際在專案用過感覺不錯,野人現曝一下~
Comments
# by kkbruce
這招好。 但這時候也能感受 ActionFilter 的強大了。 :-)
# by CIHsieh
//野人獻曝 public static TimeSpan Measure(Action action) { var stopwatch = new Stopwatch(); stopwatch.Start(); action?.Invoke(); stopwatch.Stop(); return stopwatch.Elapsed; }
# by wellxion
用using控制的話 會不會有機會無法及時被釋放的問題? 這樣的話統計出來的時間可能會有誤差
# by Jeffrey
to CIHsieh,這方法也不錯,感謝分享。 to wellxion,我倒真的沒考慮到這種可能,能否提供無法及時釋放的案例? 我來研究看看。
# by overing
如果用完馬上丟也沒要走 Disposable pattern 的話 Stopwatch.StartNew() 可以收得更短? 只是不知道 new Stopwatch() 的成本會不會被算進去? https://msdn.microsoft.com/zh-tw/library/system.diagnostics.stopwatch.startnew(v=vs.110).aspx
# by Jeffrey
to overing, 原來有這招(筆記)。查了一下,StartNew()內部就是建立Stopwatch、Start()再傳回,跟自己做效果一樣,好處是可少寫一些程式碼: public static Stopwatch StartNew() { Stopwatch s = new Stopwatch(); s.Start(); return s; } 參考: https://github.com/dotnet/corefx/blob/master/src/System.Runtime.Extensions/src/System/Diagnostics/Stopwatch.cs