算法性能[关闭]
Posted
技术标签:
【中文标题】算法性能[关闭]【英文标题】:Algorithm performance [closed] 【发布时间】:2012-11-24 16:39:28 【问题描述】:我正在计算机上测试不同参数的算法。 我注意到每个参数的性能波动。
假设我第一次运行得到 20 毫秒,第二次得到 5 毫秒,第三次得到 4 毫秒: 但是算法在这 3 次中应该是一样的。
我正在使用 C# 库中的stopwatch
来计算时间,有没有更好的方法来衡量性能而不受到这些波动的影响?
【问题讨论】:
运行一千次(或一万次)相同的代码。你会得到更稳定的结果。 嗯,第一次可能会运行各种任务,例如静态初始化程序、将数据加载到内存、JIT 编译等。第一次是一直很慢还是只是随机变慢?此外,这些数据集非常小,可能仅受其他进程、线程处理等典型系统 CPU 使用情况的影响;如果你运行它的时间更长,迭代次数多于 3 次,会发生什么? 听起来这个算法可能会使用磁盘 I/O。为什么?因为与第一次从外部磁盘获取数据相比,第二次和第三次试验看起来像是一些代码访问了缓存数据。关于分析的建议将最有帮助。 这是使用基准测试来指导您真正决定使用哪种代码的困难之一。虽然波动可能是由于内存层次结构被填满(“热缓存”),或者可能是虚拟机中的一些运行时优化,但您需要考虑这些因素可能会或可能不会总是存在于实时生产环境中。如果一个算法在多次运行后比另一个加速得更快,那么它的性能就更依赖于缓存层次结构——这是非常有价值的信息。 【参考方案1】:欢迎来到基准测试的世界。
使用在 4 毫秒内完成的代码,您将无法仅运行一次就获得准确的测量结果。页面错误、静态初始化器、JIT、CPU 缓存、分支预测器和上下文切换都会影响运行时间,并且在很小的测试中,其中任何一个都可以很容易地产生巨大的差异。循环运行它,直到秒表测量超过 1 或 2 秒,然后除以它运行的次数。
此外,您应该在启动Stopwatch
之前运行代码一次或两次,以确保代码在 CPU 的缓存中是 JIT 和热的。
当我想要一个穷人基准而不是分析时,我会使用以下代码。它比我上面描述的要多一些——比如从基准测试中移除 Stopwatch 的开销,并向下钻取直到它确信它找到了最小的执行时间。
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);
【讨论】:
【参考方案2】:首次运行需要更多时间,因为在执行时,即时 (JIT) 编译器会将 MSIL 转换为本机代码(请参阅 Managed Execution Process)。
要消除首次运行问题,您可以使用:
Ngen.exe (Native Image Generator);或运行多次迭代并跳过第一次运行的结果(但您需要确保它经过所有分支才能编译)。本机映像生成器 (Ngen.exe) 是一种提高托管应用程序性能的工具。 Ngen.exe 创建本机映像,这些文件包含已编译的处理器特定机器代码,并将它们安装到本地计算机上的本机映像缓存。运行时可以使用缓存中的本机图像,而不是使用即时 (JIT) 编译器来编译原始程序集。
要分析代码不包含StopWatch
,您可以使用Visual Studio 中包含的Performance Profiler 中的instrumentation profiling。
Visual Studio 的instrumentation profiling 方法记录了被分析应用程序中函数调用、行和指令的详细时序信息:
您还可以分析“热门路径”并比较来自多个不同运行的报告。
【讨论】:
以上是关于算法性能[关闭]的主要内容,如果未能解决你的问题,请参考以下文章
在不损失 C++ 或 Python 性能的情况下计算 pi [关闭]