使用icc、gcc和clang的同一实现性能不同的原因是什么?

使用icc、gcc和clang的同一实现性能不同的原因是什么?,gcc,assembly,x86,simd,icc,Gcc,Assembly,X86,Simd,Icc,我为她实施了一项计划,我代表她。我使用读取和存储rdtsc来测量加速比 程序如下所示,我使用x86intrin.h #define MAX1 512 #define LEN MAX1*MAX1 //array size for time measure ments int __attribute__(( aligned(32))) a[LEN]; int main(){ singleCore // It's a macro to assign the program to a si

我为她实施了一项计划,我代表她。我使用读取和存储rdtsc来测量加速比

程序如下所示,我使用
x86intrin.h

#define MAX1 512
#define LEN MAX1*MAX1  //array size for time measure ments
int __attribute__(( aligned(32))) a[LEN];

int main(){

    singleCore // It's a macro to assign the program to a single core of the processor
    int i, b, c;

    begin_rdtsc

    // b=1 and c=2 in this case
    b = 1;
    c = 2;
    i = 0;

    a[i++] = b;//0 --> a[0] = 1
    //step 1:
    //solving dependencies vectorization factor is 8
    a[i++] = a[0] + 1*c; //1  --> a[1] = 1 + 2  = 3
    a[i++] = a[0] + 2*c; //2  --> a[2] = 1 + 4  = 5
    a[i++] = a[0] + 3*c; //3  --> a[3] = 1 + 6  = 7
    a[i++] = a[0] + 4*c; //4  --> a[4] = 1 + 8  = 9
    a[i++] = a[0] + 5*c; //5  --> a[5] = 1 + 10 = 11
    a[i++] = a[0] + 6*c; //6  --> a[6] = 1 + 12 = 13
    a[i++] = a[0] + 7*c; //7  --> a[7] = 1 + 14 = 15
    // vectorization factor reached
    // 8 *c will work for all 
    //loading the results to an vector
    __m256i dep1;
    //__m256i  dep2; //  dep = { 1,   3,  5, 7,  9,  11, 13, 15 }
    __m256i coeff = _mm256_set1_epi32(8*c); //coeff = { 16, 16, 16, 16, 16, 16, 16, 16 }
    //step2
    for(; i<LEN-1; i+=8){

        dep1 = _mm256_load_si256((__m256i *) &a[i-8]);
        dep1 = _mm256_add_epi32(dep1, coeff);
        _mm256_store_si256((__m256i *) &a[i], dep1);    

    }
    end_rdtsc
    return 0;
}
  • 海湾合作委员会

    //gcc  -D _GNU_SOURCE -O3 -fno-tree-vectorize -fno-tree-slp-vectorize -march=native -c -S -o "AIC3" "AIC3.c"
    rdtsc
    salq    $32, %rdx
    movq    %r10, a(%rip)
    
    
    orq %rdx, %rax
    movq    %r9, a+8(%rip)
    movq    %r8, a+16(%rip)
    movq    %rdi, a+24(%rip)
    
    
    vmovdqa a(%rip), %ymm1
    movq    %rax, t1_rdtsc(%rip)
    movl    $a+32, %eax
    .p2align 4,,10
    .p2align 3
    .L2:
        vpaddd  %ymm1, %ymm2, %ymm0
        addq    $32, %rax
        vmovdqa %ymm0, -32(%rax)
        vmovdqa %ymm0, %ymm1
        cmpq    %rax, %rcx
        jne .L2
    rdtsc
    
  • 叮当声

    //clang  -D _GNU_SOURCE -O3 -fno-vectorize -fno-slp-vectorize -march=native -c -S -o "AIC3"clang "
    rdtsc
    shlq    $32, %rdx
    orq %rax, %rdx
    movq    %rdx, t1_rdtsc(%rip)
    movq    %r8, a(%rip)
    movq    %r9, a+8(%rip)
    movq    %r10, a+16(%rip)
    movq    %rcx, a+24(%rip)
    vmovdqa a(%rip), %ymm8
    movl    $64, %eax
    jmp .LBB0_2
    .p2align    4, 0x90
    .LBB0_9:                                #   in Loop: Header=BB0_2 Depth=2
        vpaddd  %ymm7, %ymm8, %ymm8
        vmovdqa %ymm8, a(,%rax,4)
        addq    $64, %rax
    .LBB0_2:                                #   Parent Loop BB0_1 Depth=1
                                        # =>  This Inner Loop Header: Depth=2
        vpaddd  %ymm0, %ymm8, %ymm9
        vmovdqa %ymm9, a-224(,%rax,4)
        vpaddd  %ymm1, %ymm8, %ymm9
        vmovdqa %ymm9, a-192(,%rax,4)
        vpaddd  %ymm2, %ymm8, %ymm9
        vmovdqa %ymm9, a-160(,%rax,4)
        vpaddd  %ymm3, %ymm8, %ymm9
        vmovdqa %ymm9, a-128(,%rax,4)
        vpaddd  %ymm4, %ymm8, %ymm9
        vmovdqa %ymm9, a-96(,%rax,4)
        vpaddd  %ymm5, %ymm8, %ymm9
        vmovdqa %ymm9, a-64(,%rax,4)
        vpaddd  %ymm6, %ymm8, %ymm9
        vmovdqa %ymm9, a-32(,%rax,4)
        cmpq    $16383, %rax            # imm = 0x3FFF
        jl  .LBB0_9
    # BB#3:                                 #   in Loop: Header=BB0_1 Depth=1
    rdtsc
    
  • 使用icc、gcc和clang时,加速比分别为~1.30、~4.10和4.00

    正如我提到的,我用不同的编译器编译了相同的代码,并记录了rdtsc。ICC的加速并不像我预期的那样。 我使用IACA观察内部循环,总结输出为:

    -----------------------------------------------------
    |  compilers  |    icc    |     gcc    |    clang    |
    ------------------------------------------------------
    |  Throughput |1.49 cycle |1.00 cycle  |1.49 cycle   |
    ------------------------------------------------------
    |  bottleneck | Front End | dependency | Front End   |
    ------------------------------------------------------
    
    更新-0:我比较了使用和不使用IACA生成的代码。在这种情况下,IACA没有帮助的原因是输出不相同。似乎注入IACA标记会迫使编译器停止优化,GCC生成的代码与ICC和Clang相同。但是,从吞吐量的角度来看,计算GCC中的地址更有效。总之,IACA对此代码无能为力

    更新-1:性能的输出如下所示:

    512*512
    ICC:
    
     86.06 │loop:  vpaddd 0x604580(%rax),%ymm1,%ymm0
      0.17 │       inc    %edx
      4.73 │       vmovdq %ymm0,0x6045a0(%rax)
           │       add    $0x20,%rax
           │       cmp    $0x7fff,%edx
      8.98 │       jb     loop
    
    
    GCC:
    
     30.62 │loop:  vpaddd %ymm1,%ymm2,%ymm0
     15.12 │       add    $0x20,%rax
     46.03 │       vmovdq %ymm0,-0x20(%rax)
      2.40 │       vmovdq %ymm0,%ymm1
      0.01 │       cmp    %rax,%rcx
      5.62 │       jne    loop
    
    
    LLVM:
    
      3.00 │loop:  vpaddd %ymm0,%ymm7,%ymm8                                                  
      6.61 │       vmovdq %ymm8,0x6020e0(,%rax,4)                                            
     15.96 │       vpaddd %ymm1,%ymm7,%ymm8                                                  
      5.19 │       vmovdq %ymm8,0x602100(,%rax,4)                                            
      1.89 │       vpaddd %ymm2,%ymm7,%ymm8                                                  
      6.16 │       vmovdq %ymm8,0x602120(,%rax,4)                                            
     13.25 │       vpaddd %ymm3,%ymm7,%ymm8                                                 
      8.01 │       vmovdq %ymm8,0x602140(,%rax,4)                                            
      2.10 │       vpaddd %ymm4,%ymm7,%ymm8                                                  
      5.37 │       vmovdq %ymm8,0x602160(,%rax,4)                                            
     13.92 │       vpaddd %ymm5,%ymm7,%ymm8                                                  
      7.95 │       vmovdq %ymm8,0x602180(,%rax,4)                                            
      0.89 │       vpaddd %ymm6,%ymm7,%ymm7                                                  
      4.34 │       vmovdq %ymm7,0x6021a0(,%rax,4)                                            
      2.82 │       add    $0x38,%rax                                                         
           │       cmp    $0x3ffff,%rax                                                      
      2.24 │       jl     loop                                                                
    
    ICC程序集输出显示
    rdtsc
    中有一些SIMD指令。如果我错过了什么,或者有什么不对劲,我真的不知道。我花了很多时间来认识这个问题,但没有取得任何成就。如果有人知道原因,请帮帮我。
    提前感谢。

    不同的编译器实际上使用了完全不同的实现策略

    GCC注意到,它永远不必重新加载[i-8],这是在上一次迭代中计算的,因此可以从寄存器中获取。这在某种程度上依赖于mov消除,否则reg reg移动仍然会增加一些延迟,尽管即使没有mov消除,也会比每次重新加载快得多

    ICC的codegen非常幼稚,它完全按照您编写的方式来做。存储/重新加载会增加相当多的延迟

    Clang做的事情与GCC大致相同,但是展开8(减去第一次迭代)。叮当声经常喜欢展开得更多。我不知道为什么它比GCC做的稍微差一点

    您可以通过一开始就显式不重新加载来避免重新加载:(未测试)

    dep1=\u mm256\u load\u si256((\u m256i*)和a[0]);
    
    对于(;我想你希望每个编译器在任何情况下都能产生接近最优的代码吗?如果是的话,我有一个非常好的闪亮的桥梁要卖给你,几乎是新的。@n.m.我希望ICC是个好孩子。你的报价是什么?:)ICC是单一公司的封闭源代码产品,与gcc和clang这样的OSS巨头相比没有真正的机会。唯一让它有点生存优势的是,它是同一家公司,生产目标CPU,因此他们可以尽早利用硬件知识。然后,他们又一次进行了微妙的改变,以产生相当微小的次优代码来换取AMD性能的下降,这几乎足以抵消他们的优势。但是gcc和clang现在已经足够成熟,任何一家公司的努力都是没有希望的,你不能在一个屋顶下分配如此多的专业知识。@Ped7g:ICC比gcc/clang更擅长自动矢量化某些东西。它只针对x86,而gcc在一个独立于arch的内部表示中进行矢量化,它甚至不知道目标可以有效地执行哪些洗牌。一个主要的例子是,ICC可以自动向量化搜索循环(例如,
    memchr
    的纯C实现),但gcc/clang只能自动向量化循环,其行程计数在循环进入之前确定。“叮当声通常是非常好的,不过,当它自动矢量化时。”彼得·科尔德斯,你好,彼得,想你了。在我读过的一篇基准论文中(不记得在哪里),ICC能够自动矢量化90%的循环,而Clang和GCC能够自动矢量化60%和50%。我认为克朗会赢得这场比赛。据我所知,大多数研究人员都试图用LLVM实现他们的想法,而哪个Clang使用它。我添加了
    perf
    输出,这说明了
    gcc
    Clang
    更快的原因。我认为这是因为不同的地址计算,gcc的效率更高。@Martin:
    perf
    output不能解释任何事情;IACA在这一点上是错误的,仍然适用桑迪布里奇规则。在我看来,叮当应该快得多:使用更多寄存器展开,因此循环携带的dep链仅每隔8次
    vpaddd
    。但这是整数,而不是FP,因此
    vpaddd
    延迟只有1c,它应该是存储吞吐量的瓶颈(每个时钟1个,如果L1D中不是热的,则更少)。3添加/c时不存在存储瓶颈。我使用
    IACA 3.0
    ,它默认支持
    SKL
    ,您可以将其更改为
    SKLX
    ,但clang使用非索引寻址模式更有意义。但无论如何,gcc将在前端出现瓶颈,而不是存储吞吐量。@Martin:我不是这个意思。Haswell和Skylake的IACA是错误的,因为它在SKL和SnB上为索引寻址模式建模微融合。但在真正的硬件上,它随着HSW而改变,如果您对发布的
    uops\u
    使用性能计数器,您可以看到HSW。
    512*512
    ICC:
    
     86.06 │loop:  vpaddd 0x604580(%rax),%ymm1,%ymm0
      0.17 │       inc    %edx
      4.73 │       vmovdq %ymm0,0x6045a0(%rax)
           │       add    $0x20,%rax
           │       cmp    $0x7fff,%edx
      8.98 │       jb     loop
    
    
    GCC:
    
     30.62 │loop:  vpaddd %ymm1,%ymm2,%ymm0
     15.12 │       add    $0x20,%rax
     46.03 │       vmovdq %ymm0,-0x20(%rax)
      2.40 │       vmovdq %ymm0,%ymm1
      0.01 │       cmp    %rax,%rcx
      5.62 │       jne    loop
    
    
    LLVM:
    
      3.00 │loop:  vpaddd %ymm0,%ymm7,%ymm8                                                  
      6.61 │       vmovdq %ymm8,0x6020e0(,%rax,4)                                            
     15.96 │       vpaddd %ymm1,%ymm7,%ymm8                                                  
      5.19 │       vmovdq %ymm8,0x602100(,%rax,4)                                            
      1.89 │       vpaddd %ymm2,%ymm7,%ymm8                                                  
      6.16 │       vmovdq %ymm8,0x602120(,%rax,4)                                            
     13.25 │       vpaddd %ymm3,%ymm7,%ymm8                                                 
      8.01 │       vmovdq %ymm8,0x602140(,%rax,4)                                            
      2.10 │       vpaddd %ymm4,%ymm7,%ymm8                                                  
      5.37 │       vmovdq %ymm8,0x602160(,%rax,4)                                            
     13.92 │       vpaddd %ymm5,%ymm7,%ymm8                                                  
      7.95 │       vmovdq %ymm8,0x602180(,%rax,4)                                            
      0.89 │       vpaddd %ymm6,%ymm7,%ymm7                                                  
      4.34 │       vmovdq %ymm7,0x6021a0(,%rax,4)                                            
      2.82 │       add    $0x38,%rax                                                         
           │       cmp    $0x3ffff,%rax                                                      
      2.24 │       jl     loop                                                                
    
    dep1 = _mm256_load_si256((__m256i *) &a[0]);
    for(; i<LEN-1; i+=8){
    
        dep1 = _mm256_add_epi32(dep1, coeff);
        _mm256_store_si256((__m256i *) &a[i], dep1);    
    
    }