使用icc、gcc和clang的同一实现性能不同的原因是什么?
我为她实施了一项计划,我代表她。我使用读取和存储rdtsc来测量加速比 程序如下所示,我使用使用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
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
-----------------------------------------------------
| 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);
}