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

时间:2014-08-02 13:37:21

标签: c++ floating-point benchmarking chrono

我在VC ++ 2013,Windows 7-64,Intel i7 3.6 GHz。 我想测量非常快速的数学运算的执行时间,例如我希望将标准fabsf()函数的性能与备用“更快”方法或标准tanh()与Pade近似进行比较等等。

问题是这些操作太快了,即使我运行它们数十万次,我总是在基准测试的结束和开始之间获得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 ns\n\n", std::chrono::duration_cast<std::chrono::nanoseconds>(finish-start).count());

    return 0;
}

5 个答案:

答案 0 :(得分:6)

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

我建议只保留所有计算结果的运行总和,并在打印完循环所需的时间后将其打印出来。你忽略了最后的总和,但是编译器不知道。

答案 1 :(得分:5)

为了防止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秒钟,或者多长时间。

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

答案 2 :(得分:4)

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

出于同样的原因,使用单个数字来衡量fabs之类的操作的“速度”是很困难的。特别是对于某些多发和无序处理器,执行此类操作所花费的时间在很大程度上取决于在其之前和之后执行的其他操作。

你应该看一下关于x86 / x64指令时间的Agner Fog的页面,以了解所涉及的细微差别。至于实用性,不要费心去尝试单一操作。尝试计算您实际想要使用该操作的算法。如果存在差异,您知道要使用哪个算法,并且您知道该选择是针对您的特定用例正确地进行了上下文化的。如果没有显着差异(我猜不会有),那么你知道这没关系。

答案 3 :(得分:3)

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

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

围栏是为了避免重新安排说明。

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

  1. 您可能正在使用具有涡轮增压功能的intel CPU。禁用它。 rdtsc始终根据处理器的基准时钟进行滴答。我通常会使用throttlestop。
  2. 因为您使用c ++编写,所以通常无法控制编译器生成的内容。没有什么可以避免编译器生成从内存而不是寄存器读取的cmov(条件移动)指令。
  3. 可以多种方式测量指令序列的速度。例如,SSE乘法指令在结果可用之前需要5个时钟周期('延迟')。但CPU可以在每个时钟周期发出1次乘法。每个时钟周期可以多次发出其他指令。或者需要多个时钟周期来发布。
  4. 还存在需要花费不同时间的指令问题,例如DIV或分支,或从内存中读取的任何内容。
  5. 您可能希望使用http://agner.org/optimize/#testp在指令级别运行基准测试。

答案 4 :(得分:1)

此类基准的一般策略是:

  1. 估计你期望的时间(不知何故,可能通过实验)。
  2. 编写代码以多次执行测试序列,以便结果位于适合您的计时工具的范围内(比如介于1到100秒之间)。
  3. 根据您测量的内容选择优化级别。
  4. 检查生成的代码,确保它符合您的预期。
  5. 多次运行:一次填充任何缓存,然后至少重复2次,以确保得到相同的答案。
  6. 记录循环次数和次数。
  7. 通过2或3种不同的策略进行测试,并确保在所有测试中获得一致的结果。
  8. 你会发现编译器在跳过没有用处的循环时非常狡猾。在编译器生成所需的序列之前,禁用优化和/或使代码更复杂。

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