为什么代码在线性方式下比在循环中运行速度慢?

时间:2014-02-26 02:21:13

标签: c++ c performance architecture

因此,我正在测量一些延迟,即在我的机器上执行添加指令需要多长时间来估算CPI。我首先编写了一个实现串行加法的线性版本(交错以利用管道)。然后,我接受相同的代码并将添加内容包装在一个循环中并重新评估它。我理解循环级并行的影响,但我不知道它应该如何比应该仍然实现DLP的串行版本更快。我想也许是因为循环展开版本通过寄存器重命名更多地利用了管道,因此有更高的IPC,但我也尝试增加线性版本的交错,但它并没有真正提高性能。我认为分支错误预测会导致循环版本相当慢,但事实并非如此。有什么想法吗?

#include <time.h>
#include <stdio.h>

#define ONE asm volatile( "add $20, %eax; add $10, %ecx");
#define FIVE ONE ONE ONE ONE ONE
#define TWOFIVE FIVE FIVE FIVE FIVE FIVE
#define HUNDO TWOFIVE TWOFIVE TWOFIVE TWOFIVE
#define THOUSAND  HUNDO HUNDO HUNDO HUNDO HUNDO HUNDO HUNDO HUNDO HUNDO HUNDO
#define TENTHOUSAND THOUSAND THOUSAND THOUSAND THOUSAND THOUSAND THOUSAND THOUSAND THOUSAND THOUSAND THOUSAND
#define HUNDREDK  TENTHOUSAND TENTHOUSAND TENTHOUSAND TENTHOUSAND TENTHOUSAND TENTHOUSAND TENTHOUSAND TENTHOUSAND TENTHOUSAND TENTHOUSAND
#define MILLION  HUNDREDK HUNDREDK HUNDREDK HUNDREDK HUNDREDK HUNDREDK HUNDREDK HUNDREDK HUNDREDK HUNDREDK

static __inline__ unsigned long long rdtsc(void){
    unsigned end, start;
    __asm__ __volatile__("rdtsc" : "=a"(start), "=d"(end));
    return ((unsigned long long)start) | (((unsigned long long)end)<<32);
}
int main(){
    double CPI = 0;
    long long start, end;
    long long clocks;
    int i;
    start=rdtsc();
    for(i=0; i < 10000; i++){
        HUNDREDK
    }
    end=rdtsc();
    //calculate the time elapsed in ns per access
    clocks = end-start;
    CPI = clocks/(double)(200000*10000); //divide by Number of instructions * loop

    printf("Cycles Per Instruction %lf, Clocks %Ld\n", CPI, clocks);
}

两者之间的差异非常显着。线性版本的IPC大约为0.2,而循环版本的IPC大约为4.而且我记得在评估这两个时更改了我要除的指令量:)

对于我这样做的方式可能存在一些混淆,因为文件大小不是问题。我只是删除循环。两者处理不同数量的指令,但我也改变了最后除以的值。结尾具有相同的编译大小。

更新: 谢谢你的回复。有几个问题。第一个是我进行测量的方式,一个版本的IF时间在整个循环中摊销,而另一个版本没有。我运行了一些代码,循环级并行的指令交错在循环中比在串行版本中更大。串行版本仍然有一些写入后写入依赖关系,这些依赖关系没有被重命名并导致管道停滞。

2 个答案:

答案 0 :(得分:4)

我的猜测是,因为你已经展开了如此大量的迭代,所以代码非常大。不断将新页面命令加载到缓存中的开销远高于迭代变量的开销。

就分支错误预测而言,循环实际上应该很少。它将预测最常用的分支,即9999/10000次。分支预测实际上非常好。

答案 1 :(得分:0)

更可能的原因是MILLION案例的L3缓存与HUNDREDK案例中的L1 / L2缓存没有关系。

ONE的大小介于6到8个字节之间(source) - 抱歉不精确;对于装配来说并不是那么好,但它足以用于背包计算。

考虑到这一点,并假设三个字节的最佳情况(两个add的总共六个字节):

  • HUNDREDK ~600 KB
  • MILLION ~6 MB

假设L1缓存为64 KB,L2缓存为256 KB(source),MILLION代码一直溢出到L3(CPU核心之外),而{{ 1}}主要在L1和L2中预取(在CPU核心中) - source for prefetching