【问题标题】:Benchmarking code - am I doing it right?基准代码 - 我做对了吗?
【发布时间】:2013-07-25 14:13:12
【问题描述】:

我想对 C/C++ 代码进行基准测试。我想测量 CPU 时间、挂墙时间和周期/字节。我写了一些测量函数,但是周期/字节有问题。

为了获得 CPU 时间,我编写了一个函数 getrusage()RUSAGE_SELF,对于墙上时间我使用 clock_gettimeMONOTONIC,以获得周期/字节我使用 rdtsc

我处理一个大小为 1024 的输入缓冲区:char buffer[1024]。我如何进行基准测试:

  1. 做一个热身阶段,只需拨打fun2measure(args) 1000 次:

for(int i=0; i<1000; i++) fun2measure(args);

  1. 然后,针对挂钟时间做一个实时基准测试:

    `unsigned long i; 双倍时间; 双倍时间总计 = 3.0; // 处理 3 秒

    for (timeTaken=(double)0, i=0; timeTaken

  2. 而对于cpu时间(几乎相同):

    for (timeTaken=(double)0, i=0; timeTaken <= timeTotal; timeTaken = walltime(1), i++) fun2measure(args);

但是当我想获得函数的 cpu 循环计数时,我使用了这段代码:

`unsigned long s = cyclecount();
    for (timeTaken=(double)0, i=0; timeTaken <= timeTotal; timeTaken = walltime(1), i++)
    {
        fun2measure(args);
    }
    unsigned long e = cyclecount();

unsigned long s = cyclecount();
    for (timeTaken=(double)0, i=0; timeTaken <= timeTotal; timeTaken = cputime(1), i++)
    {
        fun2measure(args);
    }
    unsigned long e = cyclecount();`

然后,计算周期/字节:((e - s) / (i * inputsSize);。这里inputsSize 是1024,因为它是buffer 的长度。但是当我将totalTime 提高到 10 秒时,我得到了奇怪的结果:

10 秒:

Did fun2measure 1148531 times in 10.00 seconds for 1024 bytes, 0 cycles/byte [CPU]
Did fun2measure 1000221 times in 10.00 seconds for 1024 bytes, 3.000000 cycles/byte [WALL]

5s:

Did fun2measure 578476 times in 5.00 seconds for 1024 bytes, 0 cycles/byte [CPU]
Did fun2measure 499542 times in 5.00 seconds for 1024 bytes, 7.000000 cycles/byte [WALL]

4s:

Did fun2measure 456828 times in 4.00 seconds for 1024 bytes, 4 cycles/byte [CPU]
Did fun2measure 396612 times in 4.00 seconds for 1024 bytes, 3.000000 cycles/byte [WALL]

我的问题:

  1. 这些结果正常吗?
  2. 为什么当我增加时间时,我的 cpu 总是 0 个周期/字节?
  3. 如何测量此类基准测试的平均时间、平均值、标准差等统计数据?
  4. 我的基准测试方法是否 100% 正常?

干杯!

第一次编辑:

i更改为double后:

Did fun2measure 1138164.00 times in 10.00 seconds for 1024 bytes, 0.410739 cycles/byte [CPU]
Did fun2measure 999849.00 times in 10.00 seconds for 1024 bytes, 3.382036 cycles/byte [WALL]

我的结果似乎还可以。所以问题 #2 不再是问题了:)

【问题讨论】:

  • 在计算周期/字节时要小心使用浮点除法
  • @VaughnCato:为什么?我应该改用i=1 吗?你的意思是我可能在这里处理zero division error
  • 如果不使用浮点除法,那么小于一的值将被四舍五入为零。
  • 另外,请注意rdtsc。我遇到了两个主要问题(也许更多):1)在许多多 CPU 系统上,TSC 计数器不保持同步,因此在起点和终点之间迁移到不同的 CPU 将给出虚假的结果,并且 2) TSC 可以可靠地(或多或少地)计算周期,但是中断、重新调度等意味着这些周期可能还没有全部用在你的代码中......不过,它可以作为一个有用的球场只要您意识到可能存在的问题,就可以估计...
  • @nullpointer 我并不是建议您不要使用它,只是要确保您了解它的局限性。它最适合用于迁移到另一个 CPU 或被其他东西中断的机会很小的短期内,或者如果您有一个大部分空闲的系统,则作为较长时间间隔的粗略估计,并且可以保证同步 TSC 或固定您的进程持续时间到特定的 CPU。

标签: c++ c performance benchmarking microbenchmark


【解决方案1】:

您的周期计数基准存在缺陷,因为它包含了 walltime/cputime 函数调用的成本。但总的来说,我强烈建议您使用适当的分析器,而不是试图重新发明轮子。特别是性能计数器会为您提供可以依赖的数字。另请注意,周期非常不可靠,因为 CPU 通常不会以固定频率运行,或者内核可能会执行任务切换并暂停您的应用一段时间。

我个人编写了基准测试,以便它们运行给定函数 N 次,因为 N 足够大,以便您获得足够的样本。然后我在外部应用诸如 linux perf 之类的分析器来让我得到一些难以推理的数字。在给定时间重复基准测试,然后您可以计算 stddev/avg 值,您可以在运行基准测试几次并评估分析器输出的脚本中执行此操作。

【讨论】:

  • 您的问题到底是什么?您可以在 Wikipedia 上找到这些值的公式。只需运行基准 N 次并收集所有值。然后在相应的公式中输入这些值...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-15
  • 1970-01-01
  • 1970-01-01
  • 2017-12-26
  • 2014-01-26
相关资源
最近更新 更多