gpt4 book ai didi

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

转载 作者:太空狗 更新时间:2023-10-29 16:41:17 24 4
gpt4 key购买 nike

我尝试使用 GCC 和内在函数优化许多紧密循环。例如考虑以下功能。

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 指令是不必要的。我们可以设置基本指针 (rsi, rdi),而不是让 rax 从零开始并在 sizeof(float)*n 结束rdx) 到数组的末尾并将 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 和内在函数已经很好地切断了我,所以我想知道是否有编译器开关或重新排序/更改我的代码的方法,以便 GCC 不生成 cmp 指令。

我尝试了以下但它仍然生成 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]))));
}
}

编辑:我感兴趣的是为适合 L1 缓存的数组(实际上是 n=2048)最大化这些函数的指令级并行性 (ILP)。尽管展开可用于提高带宽,但它会降低 ILP(假设无需展开即可获得全带宽)。

编辑:这是 Core2(Nehalem 之前)、IvyBridge 和 Haswell 系统的结果表。 Intrinsics是使用intrinsics的结果,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,我在不展开的情况下获得的结果几乎与展开时一样好,但前提是我不使用 cmp。在 AVX 上,我在不展开和不使用 cmp 的情况下获得了最佳结果。有趣的是,在 IB 上展开实际上更糟。在 Haswell 上,我通过展开获得了迄今为止最好的结果。这就是为什么我问这个 question .可以在那个问题中找到测试它的源代码。

编辑:

根据 ScottD 的回答,现在我的 Core2 系统(Nehalem 64 位模式之前)的内在函数几乎达到了 97%。我不确定为什么 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 <triad>:
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 <triad+0x3d>
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 <triad+0x24>

3a: c5 f8 77 vzeroupper
3d: c3 ret

与您手写的代码一样,gcc 在循环中使用了 5 条指令。 gcc 代码使用 scale=4,而你的代码使用 scale=1。我能够让 gcc 在 5 指令循环中使用 scale=1,但 C 代码很笨拙,循环中的 2 条 AVX 指令从 5 字节增加到 6 字节。

关于c - 在 GCC 中生成没有 cmp 指令的循环,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25921612/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com