5

我正在计算机上测试不同参数的算法。我注意到每个参数的性能波动。

假设我第一次运行得到 20 毫秒,第二次得到 5 毫秒,第三次得到 4 毫秒:但是算法在这 3 次中应该是一样的。

我正在使用stopwatchC# 库来计算时间,有没有更好的方法来衡量性能而不受到这些波动的影响?

4

2 回答 2

6

欢迎来到基准测试的世界。

使用在 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);
}
于 2012-11-24T16:44:27.237 回答
2

首次运行需要更多时间,因为在执行时,即时 (JIT) 编译器会将 MSIL 转换为本机代码(请参阅托管执行过程)。

要消除首次运行问题,您可以使用:

本机映像生成器 (Ngen.exe) 是一种提高托管应用程序性能的工具。Ngen.exe 创建本机映像,这些文件包含已编译的特定于处理器的机器代码,并将它们安装到本地计算机上的本机映像缓存中。运行时可以使用缓存中的本机映像,而不是使用即时 (JIT) 编译器来编译原始程序集。

  • 或运行多次迭代并跳过第一次运行的结果(但您需要确保它经过所有分支以编译它们)。

要在不包含StopWatch代码的情况下分析代码,可以使用Visual Studio 中包含的Performance Profiler中的检测分析。

Visual Studio的检测分析方法记录了被分析应用程序中函数调用、行和指令的详细计时信息:

经过时间

您还可以分析“热路径”并比较来自多个不同运行的报告。

热路径

于 2012-11-24T16:44:17.320 回答