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

标签 c gcc optimization assembly intrinsics

我尝试使用 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/

相关文章:

optimization - 在 fortran 中访问虚拟变量会阻止优化吗?

c - 如何使用 C 中的递归计算正弦级数(泰勒)的 x 值?

c++ - 用 Visual Studio 2005 编译 C 程序?

c++ - 为什么这个程序是用 gcc 编译的,而不是用 g++ 编译的?

python - 使用整数的鸭嘴兽优化

performance - 如何优化以下 SQL 查询以提高性能?

c# - 变量初始化不安全?

c - 尝试读入字符以保存到当前字符串,但它也被附加到前一个字符串

c - 宏中的 __VA_ARGS__ 是什么意思?

c - __attribute__((packed)) 类属性不是 GCC 特定的