c++如何对非常快的操作进行基准测试



我使用vc++ 2013, Windows 7-64, Intel i7 3.6 GHz。我想测量非常快的数学运算的执行时间,例如,我想比较标准fabsf()函数与其他"更快"方法的性能,或者标准tanh()与page近似的性能,等等。

问题是这些操作非常快,即使我运行它们无数次,在基准测试结束和开始之间我总是得到0毫秒。

我试图使用<chrono>获得以纳秒为单位的时间,但它被四舍五入到十分之一毫秒,而不是真正的纳秒,所以我在基准测试中仍然得到0经过的纳秒。

你能提供一个代码片段,我可以用它来运行我的基准吗?

这是我的:

#include <vector>
#include <chrono>
#include <ctime> 
using namespace std;
// 1/RAND_MAX
#define RAND_MAX_RECIP      0.00003051757f
int _tmain(int argc, _TCHAR* argv[])
{
    srand (static_cast <unsigned> (time(0)));
    // Fill a buffer with random float numbers
    vector<float> buffer;
    for (unsigned long i=0; i<10000000; ++i)
        buffer.push_back( (float)rand() * RAND_MAX_RECIP );
    // Get start time
    auto start = std::chrono::high_resolution_clock::now();
    for (unsigned long i=0; i<buffer.size(); ++i)
    {
        // do something with the float numbers in the buffer
    }
    // Get elapsed time
    auto finish = std::chrono::high_resolution_clock::now();
    printf("Executed in %d nsnn", std::chrono::duration_cast<std::chrono::nanoseconds>(finish-start).count());
    return 0;
}

我认为最有可能的问题是编译器注意到您没有使用计算结果,并将计算优化掉。你只需要说服编译器不要这样做。

我建议简单地保存所有计算结果的总和,并在打印完循环所花费的时间后将其打印出来。您将忽略最后的和,但编译器不会知道。

为了防止Jens提到的问题,您必须利用结果。为了解决无论我设置多少次计数器,时间总是0的问题,你采取了另一种方法。运行该操作1秒,并计算它被处理了多少次。

Psuedo代码是

   double TestFunc()
   {  
        double dSum=0, dForce=0;
        while(run)
        {
             // do test and keep the result
             // dForce += fabs(v); // whatever v is - just keep the result
             dSum +=1;  
        }
        printf("anchor answer is "+dForce) ;// this forces the compiler to generate code
        return dSum;
    }

然后运行该代码1秒,或任意长的时间。

技巧是在没有测试代码的情况下运行相同的循环,看看它迭代了多少次。然后用第二个数字减去第一个数字,看看代码(单独)花费了多长时间。

fabs()这样直接映射到指令的函数很难在综合基准测试中评估,因为与管道延迟、内存访问时间等相比,它们的执行时间非常低。例如,如果你有一个从数组中读取浮点数的循环,找到它的绝对值,然后将该值写回数组,那么在循环中执行第二次fabs()可能不会对执行时间产生影响——该算法将受内存限制,而不是cpu限制。

出于同样的原因,很难用单个数字来衡量像fabs这样的操作的"速度"。特别是对于某些多问题和乱序处理器,执行这样一个操作所花费的时间在很大程度上取决于在它之前和之后正在执行的其他操作。

您应该看一下Agner Fog关于x86/x64指令计时的页面,以了解所涉及的细微差别。至于实用性,不要费心尝试为单个操作计时。试着给你想要用到运算的算法计时。如果存在差异,您就知道要使用哪一个,并且您知道该选择对于您的特定用例来说是正确的。如果没有显著差异(我猜不会有),那么你就知道这无关紧要。

可以使用rdtsc指令在时钟周期级别计时。

uint64_t begin = __rdtsc();
_mm_lfence();
// insert your code here
_mm_lfence();
uint64_t end = __rdtsc();
uint64_t clocks = end - begin;

栅栏的存在是为了避免重新排序指令。

计时几十万次,取中值。以下陷阱适用:

  1. 你可能使用的是带有涡轮增压的英特尔CPU。禁用。rdtsc总是根据处理器的基本时钟滴答。我通常使用节流器。
  2. 因为你是用c++写的,一般来说你无法控制编译器生成的内容。没有什么可以避免编译器生成cmov(条件移动)指令,该指令从内存而不是寄存器读取。
  3. 指令序列的速度可以通过多种方式测量。例如,SSE乘法指令需要5个时钟周期才能得到结果("延迟")。但是CPU可以在每个时钟周期发出1次乘法。还有其他指令可以在每个时钟周期内多次发出。或超过一个时钟周期发出
  4. 还有一个问题是指令的时间是可变的,比如DIV,或者分支,或者任何从内存中读取的指令。

您可能希望使用http://agner.org/optimize/#testp在指令级运行基准测试。

这类基准测试的一般策略是:

  1. 估计你期望的时间(以某种方式,也许是通过实验)。
  2. 编写代码以多次执行被测序列,以便结果位于计时工具的合适范围内(例如在1到100秒之间)。
  3. 根据您要测量的内容选择优化级别。
  4. 检查生成的代码以确保它符合您的期望。
  5. 做多次运行:一次填补任何缓存,然后至少2次重复,以确保你得到相同的答案。
  6. 仔细记录循环计数和次数
  7. 通过2或3种不同的策略进行测试,并确保在所有测试中获得一致的结果。

你会发现编译器在跳过没有任何有用工作的循环时非常狡猾。禁用优化和/或使代码更复杂,直到编译器生成你想要的序列。

仔细观察管道和缓存效果。除非或直到你可以通过多种策略在多次重复中得到完全匹配的答案,否则你不能依赖结果。这是实验性的计算机科学,很难。

相关内容

  • 没有找到相关文章

最新更新