代码基准测试 - 我做得对吗?

3
我想要对C/C++代码进行基准测试。我想要测量CPU时间,墙上时间和每字节的周期数。我编写了一些测量函数,但是在计算每字节的周期数时出现了问题。
为了获得CPU时间,我编写了一个带有RUSAGE_SELF的getrusage()函数,为了获得墙上时间,我使用了带有MONOTONIC的clock_gettime函数,为了获取每字节的周期数,我使用rdtsc函数。
我处理一个大小为1024的输入缓冲区:char buffer [1024]。如何进行基准测试:
1. 进行热身阶段,只需简单地调用fun2measure(args)1000次: ``` for(int i=0; i<1000; i++) fun2measure(args); ```
2. 然后,进行实时基准测试,以获取墙上时间: ``` unsigned long i; double timeTaken; double timeTotal = 3.0; // 处理3秒钟 for (timeTaken=(double)0, i=0; timeTaken <= timeTotal; timeTaken = walltime(1), i++) fun2measure(args); ``` 3. 对于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]

5秒钟内:

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. 我的基准测试方法是否百分百正确?
干杯!
第一次编辑:
更改为之后:
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不再是问题:)

如果您不使用浮点数除法,则小于1的值将被四舍五入为零。 - Vaughn Cato
@VaughnCato:问题#2已经解决,非常感谢!您能否再多说一些其他问题? - nullpointer
3
另外,要注意rdtsc指令。我遇到过两个主要问题(也许还有其他问题):1)在许多多CPU系统上,TSC计数器不会保持同步,因此在开始和结束点之间迁移到不同的CPU将导致虚假结果;2)TSC可能可靠(或多或少)地计算周期,但是中断、重新调度等意味着这些周期可能并没有全部用于您的代码...尽管如此,只要您知道可能存在的问题,它仍然可以作为一个基本估计值。 - twalberg
@twalberg:那您是建议我不要使用rdtsc吗?还是在其之前使用cpuid指令? - nullpointer
1
@nullpointer 我并不是建议你不要使用它,而是要确保你理解它的限制。 它最好用于短时间内,在这段时间内被迁移到另一个CPU或被其他事情打断的机会很小,或者只作为更长时间间隔的粗略估计,如果您拥有大部分空闲系统并且可以保证同步TSC或将进程固定到特定CPU的持续时间。 - twalberg
显示剩余2条评论
1个回答

1
你的循环计数基准存在缺陷,因为它包括了walltime/cputime函数调用的成本。总的来说,我强烈建议您使用适当的分析工具,而不是试图重新发明轮子。特别是性能计数器将给您可靠的数字。还要注意,周期非常不可靠,因为CPU通常不以固定频率运行,或者内核可能执行任务切换并暂停您的应用程序一段时间。
我个人编写基准测试,使其运行给定函数N次,其中N足够大,以便获得足够的样本。然后在外部应用分析工具,如linux perf,以获取一些硬数据进行推理。重复基准测试一定次数后,您可以计算stddev/avg值,这可以在运行基准测试几次并评估分析工具的输出的脚本中完成。

你的问题是什么?这些值的公式可以在维基百科上找到。只需运行N次基准测试并收集所有值,然后将这些值输入相应的公式中... - milianw

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接