緣起
日前在採用 Stopwatch 計算某段程式所需執行的時間時, 發現 Stopwatch 有一些屬性可以使用: 例如: ElapsedMilliseconds , ElapsedTicks ; 而筆者以前的印象中, TimeSpan 有定義一個 TicksPerMillisecond 的屬性, 其值為 10,000; 但實際執行程式, 卻發現 ElapsedMilliseconds != ElapsedTicks / TimeSpan.TicksPerMillisecond ...問題呈現
如以下程式段落及執行結果, 很明顯的可以看出: ElapsedMilliseconds != ElapsedTicks / TimeSpan.TicksPerMillisecond ? 難道微軟的 .NET Framework 有問題?Stopwatch sw = new Stopwatch(); sw.Start(); Thread.Sleep(2000); //暫停 2 秒鐘 sw.Stop(); Console.WriteLine("1 ms is equals to {0} time ticks ", TimeSpan.TicksPerMillisecond); Console.WriteLine("Elapsed time for Thread.Sleep(2000) is {0} raw ticks ", sw.ElapsedTicks); Console.WriteLine("Elapsed time for Thread.Sleep(2000) is {0} ms ", sw.ElapsedMilliseconds);
問題追蹤
為了找出問題到底出在那裡, 所以連到 .NET Framework 的 Reference Source 查了一下 Stopwatch 這個類別的原始程式碼, 終於抓到元兇了, 原來 Stopwatch 裡 ElaspedTicks 與
TimeSpan.TicksPerMillisecond 的 ticks 是不同的; 前者是 raw ticks, 後者是 time ticks.
茲將上述的程式碼, 加入如下段落, 把 ticks 與 millisecond 轉換相關參數 IsHighResolution, Frequency 呈現出來.
其實, Frequency 代表的是每秒有多少個 raw ticks; 所以最簡單的方式, 是用 ElaspedTicks / Frequency 取得秒數後; 再用時間單位換算至 ms 或 us 或 ns. 下面這段程式, 也順便算出 '週期', 一個 ticks 代表多少奈秒.
其實, Frequency 代表的是每秒有多少個 raw ticks; 所以最簡單的方式, 是用 ElaspedTicks / Frequency 取得秒數後; 再用時間單位換算至 ms 或 us 或 ns. 下面這段程式, 也順便算出 '週期', 一個 ticks 代表多少奈秒.
Console.WriteLine("Stopwatch.Frequency={0} Stopwatch.IsHighResolution={1} ", Stopwatch.Frequency, Stopwatch.IsHighResolution); Console.ForegroundColor = ConsoleColor.Yellow; // Display the timer frequency and resolution. if (Stopwatch.IsHighResolution) { Console.WriteLine("Operations timed using the system's high-resolution performance counter."); } else { Console.WriteLine("Operations timed using the DateTime class."); } long frequency = Stopwatch.Frequency; Console.WriteLine(" Timer frequency in ticks per second = {0}", frequency); long nsPerTick = (1000L * 1000L * 1000L) / frequency; Console.WriteLine(" Timer is accurate within {0} nano-seconds (奈秒) (1/十億 秒)", nsPerTick); double usPerTick = (1000.0 * 1000.0 ) / frequency; //注意: 這裡要用 1000.0 不然只會出現整數 Console.WriteLine(" Timer is accurate within {0} micro-seconds (微秒) (1/百萬 秒)", usPerTick); double msPerTick = (1000.0) / frequency; Console.WriteLine(" Timer is accurate within {0} milli-seconds (亳秒) (1/千 秒)", msPerTick);
可參考以下對原始程式的追蹤, 而找到實際的運作狀況, 請留意 [傑士伯] 的註解說明
(1) 由 ElapsedTicks, ElapsedMilliseconds 的 get 實作方法, 可以看到, 前者是 raw tick, 後者是 time tick; 以本例的換算, 約為 1999 = (4871557* 10000 * 1000 / 2435917) / 10000 毫秒
private const long TicksPerMillisecond = 10000; private const long TicksPerSecond = TicksPerMillisecond * 1000; //[傑士伯] 10000 * 1000 = 10^7 //[傑士伯] 由以下這2個屬性, 可以看到其實是不同的, // ElapsedTicks 是執行 GetRawElapsedTicks(); // ElapsedMilliseconds 則是執行 GetElapsedDateTimeTicks()/TicksPerMillisecond public long ElapsedMilliseconds { get { return GetElapsedDateTimeTicks()/TicksPerMillisecond; } //[傑士伯](範例) (4871557 * 10000 * 1000 / 2435947) / 10000 } public long ElapsedTicks { get { return GetRawElapsedTicks(); } }
(2) 由 建構子 可以看出初始化的變數值
//[傑士伯] 建構子 static Stopwatch() { bool succeeded = SafeNativeMethods.QueryPerformanceFrequency(out Frequency); if(!succeeded) { IsHighResolution = false; Frequency = TicksPerSecond; tickFrequency = 1; } else { IsHighResolution = true; tickFrequency = TicksPerSecond; //[傑士伯] 參考前一段的 private const long TicksPerSecond 定義 tickFrequency /= Frequency; //[傑士伯](範例) 筆者的機器 10000 * 1000 / 2435947 } }
(3) 以下是 GetElapsedDateTimeTicks() 的實作
private long GetElapsedDateTimeTicks() { long rawTicks = GetRawElapsedTicks(); //[傑士伯] 這個會取得 Raw Ticks, 亦即與 ElapsedTicks 屬性相同 if( IsHighResolution) { // convert high resolution perf counter to DateTime ticks double dticks = rawTicks; dticks *= tickFrequency; //[傑士伯](範例) 筆者的機器 4871557 * 10000 * 1000 / 2435947 return unchecked((long)dticks); } else { return rawTicks; } }
總結
經由程式追蹤, 終於發現此 Ticks 非彼 Ticks; 一個是 Raw Ticks, 一個是 Time Ticks; 前者與機器效能計數器有關, 後者才是純粹用來計算時間的; 必須要作區隔, 以免誤解.
這裡順便註記一下時間單位:
這裡順便註記一下時間單位:
- 毫秒 (milli-second) = 1/千 秒, 即 1/10^3, 簡寫 ms
- 微秒 (micro-second) = 1/百萬 秒 1/10^6, 簡寫 us
- 奈秒 (nano-second) = 1/十億 秒 1/10^9, 簡寫 ns
參考文件
版本修訂
2014/08/16 修改第2張圖上方的程式段, 將 usPerTick 及 msPerTick 資料型態改為 double; 同時 1000L 也改為 1000.0, 這樣才能取得正確的小數點後數字.
2014/08/09 首次發文
2014/08/09 首次發文
讚啦!
回覆刪除謝謝 ^^
刪除