GCC 在类似架构上使用“-march=native”发出截然不同的代码

GCC emits vastly different code using "-march=native" on similar architectures

我正在用 C 编写 OpenCL 基准测试。目前,它使用 C 代码测量 CL 设备和系统处理器的融合 multiply-accumulate 性能。然后交叉检查结果的准确性。

我编写了本机代码以利用 GCC 的自动矢量化器,并且它有效。但是,我注意到 GCC 对“-march=native”标志有一些奇怪的行为。

这是我的循环:

#define BUFFER_SIZE_SQRT 4096
#define SQUARE(n) (n * n)

#define ROUNDS_PER_ITERATION 48

static float* cpu_result_matrix(const float* a, const float* b, const float* c)
{
    float* res = aligned_alloc(16, SQUARE(BUFFER_SIZE_SQRT) * sizeof(float));

    const unsigned buff_size = SQUARE(BUFFER_SIZE_SQRT);
    const unsigned round_cnt = ROUNDS_PER_ITERATION;

    float lres;
    for(unsigned i = 0; i < buff_size; i++)
    {
        lres = 0;
        for(unsigned j = 0; j < round_cnt; j++)
        {
            lres += a[i] * ((b[i] * c[i]) + b[i]);
            lres += b[i] * ((c[i] * a[i]) + c[i]);
            lres += c[i] * ((a[i] * b[i]) + a[i]);
        }

        res[i] = lres;
    }

    return res;
}

当我在 Broadwell 系统上使用“-march=native -Ofast”进行编译时,我得到了很好的矢量化 AVX 代码。

.L19:
        vmovups ymm0, YMMWORD PTR [rcx+rdx]
        mov     eax, 48
        vmovups ymm2, YMMWORD PTR [rdi+rdx]
        vaddps  ymm1, ymm0, ymm5
        vmovups ymm3, YMMWORD PTR [rsi+rdx]
        vaddps  ymm4, ymm2, ymm5
        vmulps  ymm1, ymm1, ymm2
        vfmadd132ps     ymm4, ymm1, ymm0
        vaddps  ymm1, ymm3, ymm5
        vmulps  ymm0, ymm2, ymm0
        vmulps  ymm0, ymm0, ymm1
        vfmadd132ps     ymm4, ymm0, ymm3
        vmovaps ymm1, ymm4
        vxorps  xmm0, xmm0, xmm0
        .p2align 4,,10
        .p2align 3

在 Piledriver 系统上使用相同的标志进行编译会发出 SSE2 指令,但不会发出 AVX 指令,即使体系结构支持它也是如此。 (我在这里澄清我的标题,说 Broadwell 和 Piledriver 完全不同,但它们都支持相似的向量指令集扩展,所以发出的代码应该相似。)

.L19:
        mov     eax, 48
        movups  xmm0, XMMWORD PTR [rcx+rdx]
        movups  xmm2, XMMWORD PTR [r13+0+rdx]
        movaps  xmm4, xmm0
        movaps  xmm1, xmm2
        movups  xmm3, XMMWORD PTR [rsi+rdx]
        addps   xmm4, xmm5
        addps   xmm1, xmm5
        mulps   xmm4, xmm2
        mulps   xmm1, xmm0
        mulps   xmm0, xmm2
        addps   xmm1, xmm4
        movaps  xmm4, xmm1
        mulps   xmm4, xmm3
        addps   xmm3, xmm5
        mulps   xmm0, xmm3
        addps   xmm4, xmm0
        pxor    xmm0, xmm0
        movaps  xmm1, xmm4
        .p2align 4,,10
        .p2align 3

我什至可以用 -march=broadwell 编译整个项目,运行 它在 Piledriver 系统上运行,而且它可以工作,性能提升约 100%。

我正在使用 GCC 5.1.0 进行编译,“-ftree-vectorizer-verbose”似乎不再起作用,因此编译器的行为非常不透明。我还没有找到关于该标志被弃用的任何信息,所以我不确定为什么它不再起作用,我真的很想弄清楚 GCC 在做什么。

整个项目在这里:https://github.com/jakogut/clperf/tree/v0.1

“-march=native -Q --help=target”的输出显示 AVX 和 AVX2 标志在打桩机 (bdver2) 架构上默认未启用。

布罗德韦尔:

  -mavx                                 [enabled]
  -mavx2                                [enabled]

打桩机:

  -mavx                                 [disabled]
  -mavx2                                [disabled]

AVX 被禁用,因为整个 AMD Bulldozer 系列不能有效地处理 256 位 AVX 指令。在内部,执行单元只有 128 位宽。因此 256 位操作被拆分,因此与 128 位操作相比没有任何优势。

雪上加霜的是,在 Piledriver 上,256 位存储中存在一个错误,该错误会将吞吐量降低至 about 1 every 17 cycles


您的测试用例似乎有异常。您在该关键循环中没有 256 位存储 - 这避免了该错误。这(理论上)使 SSE 与打桩机的 AVX 相提并论。

决胜局来自打桩机支持的FMA3指令。这可能就是 AVX 循环在打桩机上变得更快的原因。

您可以尝试的一件事是 -mfma4 -mtune=bdver2 看看会发生什么。