跟同事討論到 - 用 C# Stopwatch 取得效能數值,Stopwatch.ElapsedMilliseconds 只到毫秒(ms),如果需要更高的時間精確度(微秒μs,甚至奈秒ns),該怎麼做?

原以為要費番功夫,在 Stackoverlow 查到討論,答案意外地簡單。

準備測試程式如下,比較 MD5 及 SHA1 計算 1MB byte[] 雜湊值所秏費時間:

static byte[] data = new byte[1024 * 1024];
static void Main(string[] args)
{
    Test1();
    Console.ReadLine();
}

private static void Test1()
{
    Console.WriteLine("Test 1");
    for (var i = 0; i < 5; i++)
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        var md5 = MD5.Create().ComputeHash(data);
        sw.Stop();
        Console.WriteLine($"MD5 {sw.ElapsedMilliseconds}ms");
        sw.Restart();
        var sha1 = SHA1.Create().ComputeHash(data);
        sw.Stop();
        Console.WriteLine($"SHA1 {sw.ElapsedMilliseconds}ms");
    }
}

執行結果如下:

Test 1
MD5 10ms
SHA1 2ms
MD5 2ms
SHA1 2ms
MD5 2ms
SHA1 2ms
MD5 2ms
SHA1 2ms
MD5 2ms
SHA1 2ms

有兩個問題,第一是迴圈的第一次執行因涉及 .NET 初始化,耗時會異常偏高(先做 SHA1 再做 MD5,就變成第一筆 SHA1 超過 10ms),第二是 MD5 與 SHA1 執行時間相近,都是 2ms 多,用 ElapsedMilliseconds 看不出差異。

針對首次數值耗時偏差問題,除了略過第一次數據不計,我想到的另一個解法是在 Test1() 前先跑一次 MD5.Create() 完成相關初始化。至於 ElapsedMilliseconds 看不出差異問題,改用 ElapsedTicks 是種解法,但要注意,ElaspedTicks 換算成時間單位時,不是除以 TimeSpan.TicksPerMillisecond 而是依 CPU 頻率而定,需使用 Stopwatch.Frequency (每秒 Tick 數)。參考: Stopwatch.ElapsedTicks的祕密

第二版改用 ElapsedTicks * 1000000F / Stopwatch.Frequency 計算微秒(Microsecond, μs),執行前先 MD5.Create() 暖機。

static byte[] data = new byte[1024 * 1024];
static void Main(string[] args)
{
    MD5.Create();
    Test2();
    Console.ReadLine();
}

private static void Test2()
{
    Console.WriteLine("Test 2");
    for (var i = 0; i < 5; i++)
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        var md5 = MD5.Create().ComputeHash(data);
        sw.Stop();
        //
        Console.WriteLine($"MD5 {sw.ElapsedTicks * 1000000F / Stopwatch.Frequency:n3}μs");
        sw.Restart();
        var sha1 = SHA1.Create().ComputeHash(data);
        sw.Stop();
        Console.WriteLine($"SHA1 {sw.ElapsedTicks * 1000000F / Stopwatch.Frequency:n3}μs");
    }
}

執行結果的第一次時間偏長問題消失,而也呈現出 SHA1 比 MD5 計算耗時的證據。而由數值來看,精確度可到 0.1μs = 100ns。

Test 2
MD5 2,402.200μs
SHA1 2,724.000μs
MD5 2,017.300μs
SHA1 2,576.900μs
MD5 2,102.100μs
SHA1 2,578.700μs
MD5 2,024.100μs
SHA1 2,600.300μs
MD5 2,008.300μs
SHA1 2,624.300μs

自己計算麻煩了點,Stopwatch 有個 Elapsed 屬性,型別為 TimeSpan,其中 TotalMilliseconds 屬性精確度即可達到 μs 及 100ns。請看第三版:

        static byte[] data = new byte[1024 * 1024];
        static void Main(string[] args)
        {
            MD5.Create();
            Test3();
            Console.ReadLine();
        }
        private static void Test3()
        {
            Console.WriteLine("Test 3");
            for (var i = 0; i < 5; i++)
            {
                Stopwatch sw = new Stopwatch();
                sw.Start();
                var md5 = MD5.Create().ComputeHash(data);
                sw.Stop();
                Console.WriteLine($"MD5 {sw.Elapsed.TotalMilliseconds * 1000:n3}μs");
                sw.Restart();
                var sha1 = SHA1.Create().ComputeHash(data);
                sw.Stop();
                Console.WriteLine($"SHA1 {sw.Elapsed.TotalMilliseconds * 1000:n3}μs");
            }
        }

執行結果與第二版相同,但程式更簡單一些。

Test 3
MD5 2,423.400μs
SHA1 2,692.400μs
MD5 2,204.000μs
SHA1 2,976.800μs
MD5 2,094.500μs
SHA1 2,588.600μs
MD5 2,034.600μs
SHA1 2,598.900μs
MD5 2,029.900μs
SHA1 2,887.000μs

Tips abougt how to get microsecond or nanosecond precision with C# Stopwatch.


Comments

# by 5050

50

Post a comment