介紹
每個使用System.Diagnostics命名空間下的Stopwatch類做性能優化的人遲早會遇到一些麻煩。每個人都可以看到了,在同一台電腦相同功能的測試在運行時間上會有25% -30%不同。本文介紹如何使用Stopwatch類設計單線程測試程序獲得0.1% - 0.2%准確性。有了這個精度,算法才可以進行測試和比較。
背景
現代CPU有多個內核,大容量高速緩存,指令管道和許多其他的東西影響特定測試場景一個算法的運行 時間。白盒測試技術-如附加的調試器或者分析器-關閉 CPU的高速緩存線路,管道等。真正的運行時間是 隱藏的,這樣這些現代超標量處理器優化的計算方法執行速度要比使用分析器的沒有優化的還要慢(由於 更多的指令)。黑盒測試沒有附加的調試器或分析器(運行時間測量),能發現算法的實際性能,並完成 了算法的性能分析。
設置測試方案
最重要的是防止CPU內核或處理器之間的切換。對性能測試有很大的影響。這可以通過設置進程的 ProcessorAffinity來實現:
Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(2); // Use only the second core
為了能獨占CPU內核,我們必須防止其他線程可以使用此CPU內核。我們設置進程和線程的優先級,來 實現這一目標:
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
Thread.CurrentThread.Priority = ThreadPriority.Highest;
最後,但並非不重要的,在我們的測試需要熱身階段。在我的系統中,1000-1500毫秒熱身階段之後結 果是穩定的。我們可以用stopwatch自己來控制熱身(這裡至少1200mS):
stopwatch.Start();
while (stopwatch.ElapsedMilliseconds < 1200)
{
result = TestFunction(seed, count);
}
stopwatch.Stop();
下面是完整的示例:
using System;
using System.Diagnostics;
using System.Threading;
namespace PreciseMeasure
{
class Program
{
static void Main(string[] args)
{
Stopwatch stopwatch = new Stopwatch();
long seed = Environment.TickCount; // Prevents the JIT Compiler
// from optimizing Fkt calls away
long result = 0;
int count = 100000000;
Console.WriteLine("20 Tests without correct preparation");
Console.WriteLine("Warmup");
for (int repeat = 0; repeat < 20; ++repeat)
{
stopwatch.Reset();
stopwatch.Start();
result ^= TestFunction(seed, count);
stopwatch.Stop();
Console.WriteLine("Ticks: " + stopwatch.ElapsedTicks +
" mS: " +stopwatch.ElapsedMilliseconds);
}
// Uses the second Core or Processor for the Test
Process.GetCurrentProcess().ProcessorAffinity = new IntPtr (2);
// Prevents "Normal" processes
// from interrupting Threads
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
// Prevents "Normal" Threads
Thread.CurrentThread.Priority = ThreadPriority.Highest;
// from interrupting this thread
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("20 Tests with correct preparation");
Console.WriteLine("Warmup");
stopwatch.Reset();
stopwatch.Start();
while (stopwatch.ElapsedMilliseconds < 1200) // A Warmup of 1000-1500 ms
// stabilizes the CPU cache and pipeline.
{
result = TestFunction(seed, count); // Warmup
}
stopwatch.Stop();
for (int repeat = 0; repeat < 20; ++repeat)
{
stopwatch.Reset();
stopwatch.Start();
result ^= TestFunction(seed, count);
stopwatch.Stop();
Console.WriteLine("Ticks: " + stopwatch.ElapsedTicks +
" mS: " + stopwatch.ElapsedMilliseconds);
}
Console.WriteLine(result); // prevents optimizations (current compilers are
// too silly to analyze the dataflow that deep, but we never know )
}
public static long TestFunction(long seed, int count)
{
long result = seed;
for (int i = 0; i < count; ++i)
{
result ^= i ^ seed; // Some useless bit operations
}
return result;
}
}
}
結果:
沒有正確的准備
Ticks: 1580367 mS: 572 <-- highest Value
Ticks: 1577003 mS: 571
Ticks: 1576140 mS: 571
Ticks: 1560964 mS: 565
Ticks: 1351663 mS: 489
Ticks: 1248383 mS: 452
Ticks: 1115361 mS: 404
Ticks: 1112813 mS: 403
Ticks: 1113112 mS: 403
Ticks: 1112012 mS: 402 <-- lowest Value
Ticks: 1330444 mS: 482
Ticks: 1558493 mS: 564
Ticks: 1501097 mS: 543
Ticks: 1517796 mS: 549
Ticks: 1542712 mS: 558
Ticks: 1574959 mS: 570
Ticks: 1483975 mS: 537
Ticks: 1390578 mS: 503
Ticks: 1546904 mS: 560
Ticks: 1349507 mS: 488
運行時間在402ms572ms之間不等。存在170 mS 或者42%差距。很顯然,這些結果是沒有用的。
正確的准備:
Ticks: 1110518 mS: 402
Ticks: 1110540 mS: 402
Ticks: 1110543 mS: 402
Ticks: 1110684 mS: 402 <-- highest Value
Ticks: 1110508 mS: 402
Ticks: 1110553 mS: 402
Ticks: 1110600 mS: 402
Ticks: 1110433 mS: 402 <-- lowest Value
Ticks: 1110509 mS: 402
Ticks: 1110508 mS: 402
Ticks: 1110489 mS: 402
Ticks: 1110568 mS: 402
Ticks: 1110503 mS: 402
Ticks: 1110566 mS: 402
Ticks: 1110625 mS: 402
Ticks: 1110474 mS: 402
Ticks: 1110571 mS: 402
Ticks: 1110448 mS: 402
Ticks: 1110555 mS: 402
Ticks: 1110495 mS: 402
有20個相同的結果:402 ms ,只能用tick(內部CPU性能計數器值)分辨。測試結果存在251tick或者 0,02 %差距。在我的系統中,Stopwatch的頻率是每秒2760029 tick。測試之間的運行差別只有0,09毫秒 。這用於衡量和比較算法運行是非常好的。
興趣點:
其中一個很重要的事情應該牢記。沒有做准備的最好(最低)結果值還沒有做了准備的最差結果值好 。CPU的上下文和核心交換對應用程序運行會產生巨大影響。
英文版:http://www.codeproject.com/KB/testing/stopwatch-measure-precise.aspx