在GCC中生成没有cmp指令的循环

我有一些紧凑的循环,我正在尝试使用GCC和内在函数进行优化。 考虑例如以下function。

void triad(float *x, float *y, float *z, const int n) { float k = 3.14159f; int i; __m256 k4 = _mm256_set1_ps(k); for(i=0; i<n; i+=8) { _mm256_store_ps(&z[i], _mm256_add_ps(_mm256_load_ps(&x[i]), _mm256_mul_ps(k4, _mm256_load_ps(&y[i])))); } } 

这会产生这样的主循环

 20: vmulps ymm0,ymm1,[rsi+rax*1] 25: vaddps ymm0,ymm0,[rdi+rax*1] 2a: vmovaps [rdx+rax*1],ymm0 2f: add rax,0x20 33: cmp rax,rcx 36: jne 20 

但是cmp指令是不必要的。 而不是让rax从零开始并在sizeof(float)*n处完成,我们可以将基本指针( rsirdirdx )设置为数组的末尾,并将rax设置为-sizeof(float)*n然后测试为零。 我可以用我自己的汇编代码这样做

 .L2 vmulps ymm1, ymm2, [rdi+rax] vaddps ymm0, ymm1, [rsi+rax] vmovaps [rdx+rax], ymm0 add rax, 32 jne .L2 

但我无法让GCC这样做。 我现在有几个测试,这会产生重大影响。 直到最近,GCC和内在函数已经让我很好,所以我想知道是否有编译器开关或重新排序/更改我的代码的方法,因此cmp指令不是用GCC生成的。

我尝试了以下但它仍然产生cmp 。 我尝试的所有变化仍然产生cmp

 void triad2(float *x, float *y, float *z, const int n) { float k = 3.14159f; float *x2 = x+n; float *y2 = y+n; float *z2 = z+n; int i; __m256 k4 = _mm256_set1_ps(k); for(i=-n; i<0; i+=8) { _mm256_store_ps(&z2[i], _mm256_add_ps(_mm256_load_ps(&x2[i]), _mm256_mul_ps(k4, _mm256_load_ps(&y2[i])))); } } 

编辑:我感兴趣的是为这些函数最大化指令级并行(ILP),这些函数适合L1缓存(实际上是n=2048 )。 虽然展开可用于改善带宽,但它可以降低ILP(假设可以在不展开的情况下获得全带宽)。

编辑:这是Core2(前Nehalem),IvyBridge和Haswell系统的结果表。 内在函数是使用内在函数的结果,unroll1是我的汇编代码不使用cmp ,而unroll16是我的汇编代码展开16次。 百分比是峰值性能的百分比(频率* num_bytes_cycle,其中num_bytes_cycle对于SSE为24,对于AVX为48,对于FMA为96)。

  SSE AVX FMA intrinsic 71.3% 90.9% 53.6% unroll1 97.0% 96.1% 63.5% unroll16 98.6% 90.4% 93.6% ScottD 96.5% 32B code align 95.5% 

对于SSE,我得到的结果几乎与unrol一样好,只有当我不使用cmp才能展开。 在AVX上,我得到了最好的结果,没有展开,也没有使用cmp 。 有趣的是,在IB展开实际上更糟糕。 在Haswell上,我通过展开得到了最好的结果。 这就是我问这个问题的原因 。 可以在该问题中找到测试它的源代码。

编辑:

基于ScottD的答案,我现在得到了近97%的内部函数用于我的Core2系统(在Nehalem 64位模式之前)。 我不确定为什么cmp实际上很重要,因为无论如何它应该每次迭代需要2个时钟周期。 对于Sandy Bridge来说,效率损失是由于代码对齐而不是额外的cmp 。 在Haswell上,无论如何只能展开作品。

这个怎么样。 编译器是gcc 4.9.0 mingw x64:

 void triad(float *x, float *y, float *z, const int n) { float k = 3.14159f; intptr_t i; __m256 k4 = _mm256_set1_ps(k); for(i = -n; i < 0; i += 8) { _mm256_store_ps(&z[i+n], _mm256_add_ps(_mm256_load_ps(&x[i+n]), _mm256_mul_ps(k4, _mm256_load_ps(&y[i+n])))); } } 

gcc -c -O3 -march = corei7 -mavx2 triad.c

 0000000000000000 : 0: 44 89 c8 mov eax,r9d 3: f7 d8 neg eax 5: 48 98 cdqe 7: 48 85 c0 test rax,rax a: 79 31 jns 3d  c: c5 fc 28 0d 00 00 00 00 vmovaps ymm1,YMMWORD PTR [rip+0x0] 14: 4d 63 c9 movsxd r9,r9d 17: 49 c1 e1 02 shl r9,0x2 1b: 4c 01 ca add rdx,r9 1e: 4c 01 c9 add rcx,r9 21: 4d 01 c8 add r8,r9 24: c5 f4 59 04 82 vmulps ymm0,ymm1,YMMWORD PTR [rdx+rax*4] 29: c5 fc 58 04 81 vaddps ymm0,ymm0,YMMWORD PTR [rcx+rax*4] 2e: c4 c1 7c 29 04 80 vmovaps YMMWORD PTR [r8+rax*4],ymm0 34: 48 83 c0 08 add rax,0x8 38: 78 ea js 24  3a: c5 f8 77 vzeroupper 3d: c3 ret 

就像你手写的代码一样,gcc使用5条循环指令。 gcc代码使用scale = 4,其中你使用scale = 1。 我能够让gcc使用scale = 1和5指令循环,但C代码很笨拙,循环中的2个AVX指令从5个字节增长到6个字节。

英特尔Ivy Bridge或更高版本上的指令解码器可以将cmp和jne融合到管道中的单个操作中(称为宏操作融合),因此在这些最近的处理器上cmp应该消失。

最终代码:

 #define SF sizeof(float) #ifndef NO //floats per vector, compile with -DNO = 1,2,4,8,... #define NO 8 //MUST be power of two #endif void triadfinaler(float const *restrict x, float const *restrict y, \ float *restrict z, size_t n) { float *restrict d = __builtin_assume_aligned(z, NO*SF); //gcc builtin, float const *restrict m = __builtin_assume_aligned(y, NO*SF); //optional but produces float const *restrict a = __builtin_assume_aligned(x, NO*SF); //better code float const k = 3.14159f; n*=SF; while (n &= ~((size_t)(NO*SF)-1)) //this is why NO*SF must be power of two { size_t nl = n/SF; for (size_t i = 0; i 

我更喜欢让编译器选择指令,而不是使用内在函数(尤其是因为你使用了intel-intrinsics,gcc并不喜欢)。 无论如何,以下代码在gcc 4.8上为我生成了很好的程序集:

 void triad(float *restrict x, float *restrict y, float *restrict z, size_t n) //I hope you weren't aliasing any function arguments... Oh, an it's void, not float { float *restrict d = __builtin_assume_aligned(z, 32); // Uh, make sure your arrays float *restrict m = __builtin_assume_aligned(y, 32); // are aligned? Faster that way float *restrict a = __builtin_assume_aligned(x, 32); // float const k = 3.14159f; while (n &= ~((size_t)0x7)) //black magic, causes gcc to omit code for non-multiples of 8 floats { n -= 8; //You were always computing on 8 floats at a time, right? d[n+0] = k * m[n+0] + a[n+0]; //manual unrolling d[n+1] = k * m[n+1] + a[n+1]; d[n+2] = k * m[n+2] + a[n+2]; d[n+3] = k * m[n+3] + a[n+3]; d[n+4] = k * m[n+4] + a[n+4]; d[n+5] = k * m[n+5] + a[n+5]; d[n+6] = k * m[n+6] + a[n+6]; d[n+7] = k * m[n+7] + a[n+7]; } } 

这为我的corei7avx2生成了很好的代码,带有-O3:

 triad: andq $-8, %rcx je .L8 vmovaps .LC0(%rip), %ymm1 .L4: subq $8, %rcx vmovaps (%rsi,%rcx,4), %ymm0 vfmadd213ps (%rdi,%rcx,4), %ymm1, %ymm0 vmovaps %ymm0, (%rdx,%rcx,4) andq $-8, %rcx jne .L4 vzeroupper .L8: rep ret .cfi_endproc .LC0: .long 1078530000 .long 1078530000 .long 1078530000 .long 1078530000 .long 1078530000 .long 1078530000 .long 1078530000 .long 1078530000 

编辑:我有点失望的编译器没有优化这个代码到最后一条指令,所以我搞砸了一点。 只是改变循环中的事物的顺序摆脱了编译器发出的AND ,这让我走上了正确的轨道。 然后我只需要让它不要在循环中进行不必要的地址计算。 叹。

 void triadtwo(float *restrict x, float *restrict y, float *restrict z, size_t n) { float *restrict d = __builtin_assume_aligned(z, 32); float *restrict m = __builtin_assume_aligned(y, 32); float *restrict a = __builtin_assume_aligned(x, 32); float const k = 3.14159f; n<<=2; while (n &= -32) { d[(n>>2)-8] = k * m[(n>>2)-8] + a[(n>>2)-8]; d[(n>>2)-7] = k * m[(n>>2)-7] + a[(n>>2)-7]; d[(n>>2)-6] = k * m[(n>>2)-6] + a[(n>>2)-6]; d[(n>>2)-5] = k * m[(n>>2)-5] + a[(n>>2)-5]; d[(n>>2)-4] = k * m[(n>>2)-4] + a[(n>>2)-4]; d[(n>>2)-3] = k * m[(n>>2)-3] + a[(n>>2)-3]; d[(n>>2)-2] = k * m[(n>>2)-2] + a[(n>>2)-2]; d[(n>>2)-1] = k * m[(n>>2)-1] + a[(n>>2)-1]; n -= 32; } } 

丑陋的代码? 是。 但是大会

 triadtwo: salq $2, %rcx andq $-32, %rcx je .L54 vmovaps .LC0(%rip), %ymm1 .L50: vmovaps -32(%rsi,%rcx), %ymm0 vfmadd213ps -32(%rdi,%rcx), %ymm1, %ymm0 vmovaps %ymm0, -32(%rdx,%rcx) subq $32, %rcx jne .L50 vzeroupper .L54: rep ret .cfi_endproc .LC0: .long 1078530000 .long 1078530000 .long 1078530000 .long 1078530000 .long 1078530000 .long 1078530000 .long 1078530000 .long 1078530000 

Mmmmhhh ,循环中的五个指令,宏观可融合的减法和分支......