高精度性能测试指南

在现代计算机系统中,CPU拥有多核心、大缓存、指令流水线等特性,这些因素都可能影响算法在特定测试场景中的运行时间。白盒测试技术,如附加的调试器或分析器,可能会忽略CPU上的缓存行、流水线等,从而隐藏了实际的运行时间。因此,针对这些现代超标量CPU优化的算法可能会因为附加的分析器而表现得比未优化的算法更慢。而黑盒测试(运行时间测量)则可以在不附加调试器或分析器的情况下,发现算法的真实性能,并完成对算法性能的分析。

测试场景的设置

最重要的是要防止在CPU核心或处理器之间切换。切换会忽略缓存等,并对测试性能产生巨大影响。这可以通过设置进程的ProcessorAffinity掩码来实现:

Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(2);

为了更独占地使用CPU核心,必须防止其他线程使用这个CPU核心。可以通过设置进程和线程的优先级来实现这一点:

Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High; Thread.CurrentThread.Priority = ThreadPriority.Highest;

最后但同样重要的是,需要在测试中加入预热阶段。在系统上,经过1000-1500毫秒的预热后,结果就稳定了。可以使用Stopwatch本身来控制预热(至少1200毫秒):

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; 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); } Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(2); Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High; Thread.CurrentThread.Priority = ThreadPriority.Highest; Console.WriteLine(); Console.WriteLine("20 Tests with correct preparation"); Console.WriteLine("Warmup"); stopwatch.Reset(); stopwatch.Start(); while (stopwatch.ElapsedMilliseconds < 1200) { result = TestFunction(seed, count); } 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); } public static long TestFunction(long seed, int count) { long result = seed; for (int i = 0; i < count; ++i) { result ^= i ^ seed; } return result; } } }

结果

未正确准备的情况下:

Ticks: 1580367 mS: 572 <-- 最高值

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 <-- 最低值

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

运行时间在402毫秒到572毫秒之间,相差170毫秒或42%。这些结果没有参考价值。

正确准备的情况下:

Ticks: 1110518 mS: 402

Ticks: 1110540 mS: 402

Ticks: 1110543 mS: 402

Ticks: 1110684 mS: 402 <-- 最高值

Ticks: 1110508 mS: 402

Ticks: 1110553 mS: 402

Ticks: 1110600 mS: 402

Ticks: 1110433 mS: 402 <-- 最低值

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毫秒,只能通过tick计数(内部CPU性能计数器值)来区分。差异是251个ticks或0.02%。在系统上,Stopwatch的频率是每秒2760029个ticks。测试运行之间的差异仅为0.09毫秒。这是非常好的,可以用来测量和比较算法的运行时间。

注意事项

非常重要的一点是,未准备情况下的最好(最低)值并不比准备情况下的最差值好。CPU上下文和核心切换对应用程序的运行时间有巨大的影响。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485