我正在计算机上测试不同参数的算法。我注意到每个参数的性能波动。
假设我第一次运行得到 20 毫秒,第二次得到 5 毫秒,第三次得到 4 毫秒:但是算法在这 3 次中应该是一样的。
我正在使用stopwatch
C# 库来计算时间,有没有更好的方法来衡量性能而不受到这些波动的影响?
我正在计算机上测试不同参数的算法。我注意到每个参数的性能波动。
假设我第一次运行得到 20 毫秒,第二次得到 5 毫秒,第三次得到 4 毫秒:但是算法在这 3 次中应该是一样的。
我正在使用stopwatch
C# 库来计算时间,有没有更好的方法来衡量性能而不受到这些波动的影响?
欢迎来到基准测试的世界。
使用在 4 毫秒内完成的代码,您将无法仅运行一次就获得准确的测量结果。页面错误、静态初始化器、JIT、CPU 缓存、分支预测器和上下文切换都会影响运行时间,并且在很小的测试中,其中任何一个都可以很容易地产生巨大的差异。循环运行它,直到秒表测量超过 1 或 2 秒,然后除以它运行的次数。
此外,您应该在开始之前运行代码一次或两次,Stopwatch
以确保代码在 CPU 的缓存中是 JIT 和热的。
当我想要一个穷人基准而不是分析时,这是我使用的代码。它比我上面描述的要多一点——比如从基准测试中删除秒表的开销,并向下钻取直到它确信它找到了最小的执行时间。
runCount = 检查下一个最小执行时间的次数。
subRunCount = 如果你传入的 Action 已经运行了一个循环,或者运行在多个项目上等,并且你想测量每个项目花费的时间,把计数放在这里。
static double Benchmark(string name, int runCount, int subRunCount, Action action)
{
Console.WriteLine("{0}: warming up...", name);
// warm up.
action();
Console.WriteLine("{0}: finding ballpark speed...", name);
// find an average amount of calls it fill up two seconds.
Stopwatch sw = Stopwatch.StartNew();
int count = 0;
do
{
++count;
action();
}
while (sw.ElapsedTicks < (Stopwatch.Frequency * 2));
sw.Stop();
Console.WriteLine("{0}: ballpark speed is {1} runs/sec", name, MulMulDiv(count, subRunCount, Stopwatch.Frequency, sw.ElapsedTicks));
// The benchmark will run the Action in a loop 'count' times.
count = Math.Max(count / 2, 1);
// Start the benchmark.
Console.Write("{0}: benchmarking", name);
Console.Out.Flush();
long minticks = long.MaxValue;
int runs = 0;
while (runs < runCount)
{
sw.Restart();
for (int i = 0; i < count; ++i)
{
action();
}
sw.Stop();
long ticks = sw.ElapsedTicks;
if (ticks < minticks)
{
// Found a new smallest execution time. Reset.
minticks = ticks;
runs = 0;
Console.Write('+');
Console.Out.Flush();
continue;
}
else
{
++runs;
Console.Write('.');
Console.Out.Flush();
}
}
Console.WriteLine("done");
Console.WriteLine("{0}: speed is {1} runs/sec", name, MulMulDiv(count, subRunCount, Stopwatch.Frequency, minticks));
return (double)count * subRunCount * Stopwatch.Frequency / minticks;
}
static long MulMulDiv(long count, long subRunCount, long freq, long ticks)
{
return (long)((BigInteger)count * subRunCount * freq / ticks);
}
首次运行需要更多时间,因为在执行时,即时 (JIT) 编译器会将 MSIL 转换为本机代码(请参阅托管执行过程)。
要消除首次运行问题,您可以使用:
本机映像生成器 (Ngen.exe) 是一种提高托管应用程序性能的工具。Ngen.exe 创建本机映像,这些文件包含已编译的特定于处理器的机器代码,并将它们安装到本地计算机上的本机映像缓存中。运行时可以使用缓存中的本机映像,而不是使用即时 (JIT) 编译器来编译原始程序集。
要在不包含StopWatch
代码的情况下分析代码,可以使用Visual Studio 中包含的Performance Profiler中的检测分析。
Visual Studio的检测分析方法记录了被分析应用程序中函数调用、行和指令的详细计时信息:
您还可以分析“热路径”并比较来自多个不同运行的报告。