间接成本〜浮动乘法的3倍,真的吗? (带演示)

时间:2017-05-25 03:13:42

标签: c++ optimization c++14 indirection

我刚发现间接花费大约是浮动乘法的3倍! 这是预期的吗?我的考试错了吗?

背景

在我阅读How much does pointer indirection affect efficiency?后,我对间接成本感到恐慌。

  

由于现代CPU的工作原理,通过指针间接可能要慢得多。

在我过早优化我的真实代码之前,我想确保它真的花费很多,因为我担心。

我做了一些技巧来找到粗略数字(3x),如下所示: -

第1步

  • 测试1 :无间接 - >计算一些东西
  • 测试2 :间接 - >计算一些东西(相同)

我发现 Test2 需要更多时间 Test1
这里没什么好惊讶的。

第2步

  • 测试1 :无间接 - >计算一些昂贵的东西
  • 测试2 :间接 - >计算便宜的东西

我尝试将calculate something expensive中的代码更改为更加昂贵,以使测试成本接近相同。

结果

最后,我发现使两个测试使用相同的时间(即收支平衡)的可能功能之一是: -

  • 测试1 :无间接 - >返回float*float*... 3次
  • 测试2 :间接 - >只需返回float
  • 即可

以下是我的测试用例(ideone demo): -

class C{
    public: float hello;  
    public: float hello2s[10];  
    public: C(){
        hello=((double) rand() / (RAND_MAX))*10;
        for(int n=0;n<10;n++){
            hello2s[n]= ((double) rand() / (RAND_MAX))*10;
        }
    }
    public: float calculateCheap(){
        return hello;
    }
    public: float calculateExpensive(){
        float result=1;
        result=hello2s[0]*hello2s[1]*hello2s[2]*hello2s[3]*hello2s[4];
        return result;
    }
};

这是主要的: -

int main(){
    const int numTest=10000;
    C  d[numTest];
    C* e[numTest];
    for(int n=0;n<numTest;n++){
        d[n]=C();
        e[n]=new C();
    }
    float accu=0;
    auto t1= std::chrono::system_clock::now();
    for(int n=0;n<numTest;n++){
        accu+=d[n].calculateExpensive();  //direct call
    }
    auto t2= std::chrono::system_clock::now();
    for(int n=0;n<numTest;n++){
        accu+=e[n]->calculateCheap();     //indirect call
    }
    auto t3= std::chrono::system_clock::now();
    std::cout<<"direct call time ="<<(t2-t1).count()<<std::endl;
    std::cout<<"indirect call time ="<<(t3-t2).count()<<std::endl;
    std::cout<<"print to disable compiler cheat="<<accu<<std::endl;
}

直接通话时间间接通话时间的调整与上述相似(通过编辑calculateExpensive)。

结论

间接成本= 3倍浮动乘法 在我的桌面(带有-O2的Visual Studio 2015)中,它是7x。

问题

是否可以预期间接成本约为浮动乘法的3倍? 如果不是,我的测试怎么错了?

(感谢enhzflep建议进行改进,将其编辑。)

3 个答案:

答案 0 :(得分:6)

简而言之,您的测试非常具有代表性,并且实际上并未准确衡量您的想法。

请注意,您拨打new C() 100&000,000次。这将在你的内存中创建100个&n; 39个C散布实例,每个实例都非常小。如果您的内存访问是常规的,现代硬件非常擅长预测。由于每次分配,每次调用new都与其他分配无关,因此内存地址不能很好地组合在一起,这使得预测更加困难。这导致所谓的缓存未命中。

分配为数组(new C[numTest])可能会产生完全不同的结果,因为在这种情况下地址可以再次预测。尽可能地将您的记忆分组并以线性,可预测的方式访问它通常会提供更好的性能。这是因为大多数缓存和地址预取器都期望这种模式恰好出现在常见程序中。

次要添加:像C d[numTest] = {};一样初始化将在每个元素上调用构造函数

答案 1 :(得分:3)

您的问题没有简单的答案。这取决于硬件的功能和特性(CPU,RAM,总线速度等)。

在过去,浮点数倍数可能需要数十甚至数百个周期。内存访问的速度与CPU频率相似(在这里考虑MegaHertz),浮点乘法需要比间接更长的时间。

从那时起,情况发生了很大变化。现代硬件可以在一个或两个周期内执行浮点乘法,而间接(存储器访问)可能只需要几个周期到数百个,具体取决于要读取的数据所在的位置。可以有多个级别的缓存。在极端情况下,通过间接访问的内存已经交换到磁盘,需要重新读回。这将有数千个周期的延迟。

通常,获取浮点乘法和解码指令的操作数的开销可能需要比实际乘法更长的时间。

答案 2 :(得分:2)

间接成本主要由缓存未命中率决定。老实说,Cache Misses比你说的其他任何东西都贵得多,其他一切最终都是舍入错误。

缓存未命中和间接可能比测试表明的要贵得多。

这主要是因为您只有100,000个元素,并且CPU缓存可以缓存每个这些浮点数。顺序堆分配将倾向于聚集。

你会得到一堆缓存未命中,但不是每个元素都有一个。

你的两个案件都是间接的。 “间接”情况必须遵循两个指针,而“直接”情况必须做一个指针算术实例。 “昂贵”的情况可能适用于某些SIMD,特别是如果您具有宽松的浮点精度(允许乘法重新排序)。

如所见herethis image(不内联,我缺乏权利),主内存引用的数量将主导上述代码中的几乎任何其他内容。 2 Ghz CPU的周期时间为0.5 ns,主存储器参考为100 ns或200个周期的延迟。

同时,如果你可以提取矢量化代码,桌面CPU每个周期可以达到8+浮点运算。与单个高速缓存未命中相比,这是一个可能快1600倍的浮点运算。

间接可能会花费你能够使用矢量化指令(8倍减速),如果一切都在缓存中,仍然需要比替代方案更频繁地需要L2缓存引用(14x减速)。但与200 ns主存储器参考延迟相比,这些减速很小。

请注意,并非所有的CPU都具有相同的矢量化水平,因此需要付出一些努力来加速CPU /主存延迟,FPU具有不同的特性以及无数其他复杂情况。