基准测试,代码重新排序,易失性

Adr*_*thy 17 c++ benchmarking volatile compiler-optimization

我决定要对特定函数进行基准测试,所以我天真地编写如下代码:

#include <ctime>
#include <iostream>

int SlowCalculation(int input) { ... }

int main() {
    std::cout << "Benchmark running..." << std::endl;
    std::clock_t start = std::clock();
    int answer = SlowCalculation(42);
    std::clock_t stop = std::clock();
    double delta = (stop - start) * 1.0 / CLOCKS_PER_SEC;
    std::cout << "Benchmark took " << delta << " seconds, and the answer was "
              << answer << '.' << std::endl;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

一位同事指出,我应该声明startstop变量volatile以避免代码重新排序.他建议优化器可以,例如,有效地重新排序代码,如下所示:

    std::clock_t start = std::clock();
    std::clock_t stop = std::clock();
    int answer = SlowCalculation(42);
Run Code Online (Sandbox Code Playgroud)

起初我对这种极端重新排序是允许的持怀疑态度,但经过一些研究和实验,我才知道它是.

但挥发性并不是正确的解决方案; 实际上只是内存映射I/O不易变?

尽管如此,我补充说volatile,不仅基准测试需要更长的时间,而且从运行到运行也非常不一致.没有易失性(并且很幸运,以确保代码没有重新排序),基准测试一直需要600-700毫秒.对于易失性,它通常需要1200毫秒,有时超过5000毫秒.除了不同的寄存器选择外,两个版本的反汇编列表几乎没有差别.这让我想知道是否有另一种方法来避免没有这种压倒性副作用的代码重新排序.

我的问题是:

在像这样的基准测试代码中阻止代码重新排序的最佳方法是什么?

我的问题类似于这个问题(关于使用volatile来避免省略而不是重新排序),这个问题(没有回答如何防止重新排序),以及这一问题(辩论问题是代码重新排序还是死代码)消除).虽然这三个都是关于这个确切的话题,但实际上没有人回答我的问题

更新:答案似乎是我的同事错了,这样的重新排序与标准不一致.我赞成所有这样说的人,并且正在向Maxim发放奖金.

我已经看过一个案例(基于这个问题中的代码),其中Visual Studio 2010重新排序时钟调用(如图所示)(仅限64位版本).我试图做一个最小的案例来说明这样我就可以在Microsoft Connect上提交一个bug.

对于那些说volatile应该慢得多的人,因为它强制读取和写入内存,这与发出的代码不太一致.在我对这个问题的回答中,我展示了包含和不包含volatile的代码的反汇编.在循环内部,一切都保存在寄存器中.唯一显着的差异似乎是寄存器选择.我不太了解x86组装,以便知道为什么非易失性版本的性能始终如一,而易失性版本不一致(有时甚至是显着)慢.

Max*_*kin 17

一位同事指出,我应该将start和stop变量声明为volatile,以避免代码重新排序.

对不起,但你的同事错了.

编译器不会重新排序对编译时定义不可用的函数的调用.简单地想象一下,如果编译器重新排序这些调用fork和/ exec或围绕这些调用移动代码,那么随之而来的欢闹.

换句话说,没有定义的任何函数都是编译时内存屏障,也就是说,编译器不会在调用之前移动后续语句或在调用之后移动先前语句.

在您的代码调用中,std::clock最终调用其定义不可用的函数.

我不能建议观看原子武器:C++内存模型和现代硬件,因为它讨论了关于(编译时)内存障碍的误解以及volatile许多其他有用的东西.

尽管如此,我添加了volatile并发现不仅基准测试需要更长的时间,而且从运行到运行也非常不一致.没有易失性(并且很幸运,以确保代码没有重新排序),基准测试一直需要600-700毫秒.对于易失性,它通常需要1200毫秒,有时超过5000毫秒

不确定是否volatile应该责怪这里.

报告的运行时间取决于基准测试的运行方式.确保禁用CPU频率缩放,以便它不会打开turbo模式或在运行过程中切换频率.此外,微基准测试应作为实时优先级过程运行,以避免调度噪声.可能是在另一次运行期间,一些后台文件索引器开始与您的CPU时间基准竞争.有关详细信息,请参阅

一个好的做法是测量多次执行函数所需的时间并报告min/avg/median/max/stdev/total time number.高标准偏差可能表明不进行上述准备.第一次运行通常是最长的,因为CPU缓存可能是冷的,它可能需要许多高速缓存未命中和页面错误,并从第一次调用共享库解析动态符号(延迟符号分辨率为默认的运行时间在Linux上链接方式,例如),而后续调用将以更少的开销执行.

  • 它不会重新排序对std :: clock()的调用,但它可以内联并将调用移动到SlowCalculation(),无论它在哪里(并且经常).为什么人们会使用障碍? (2认同)

归档时间:

查看次数:

4121 次

最近记录:

6 年,9 月 前