是否有可能影响 GCC/Clang/Intel ICPC XMM/YMM 寄存器分配?

Yan*_*hou 5 assembly gcc clang icc intrinsics

我有一个高度优化的函数,在内循环中重复调用,用 SSE2/AVX2 加速编写。经过一些改进,现在我正在接近理论上的最佳性能(基于指令延迟和吞吐量)。然而,性能不太便携。问题在于有超过16个__m128i/__256i变量。当然,其中只有 16 个可以分配在寄存器中,其余的分配在堆栈中。该功能或多或少类似于以下内容,

void eval(size_t n, __m128i *rk /* other inputs */)
{
    __m128i xmmk0 = rk[0];
    // ...
    __m128i xmmk6 = rk[6];
    __m128i xmmk;

    __m128i xmmk[Rounds - 6];
    // copy rk[7] to r[Rounds] to xmmk

    while (n >= 8) {
        n -= 8;

        __m128i xmm0 = /* initialize state xmm0 */
        // do the same for xmm1 - xmm7

        // round 0
        xmm0 = /* a few instructions involving xmm0 and xmmk0 */;
        // do the same for xmm1 - xmm7

        // do the same for round 1 to 6, using xmmk1, ..., xmmk6

        // round 7, copy xmmk[0] to a temporary __m128i variable
        xmm0 = /* a few instructions involving xmm0 and xmmk[0] */;
        // do the same for xmm1 - xmm7

        // do the same for round 7 to Rounds, using xmmk[1], xmmk[Rounds - 7]
    }
}
Run Code Online (Sandbox Code Playgroud)

涉及的变量超过 16 个__m128i。我可以获得的最佳性能是当编译器将前 7 个轮常量分配到xmm0寄存器xmm7中,并使用剩余的寄存器作为加载剩余轮的轮常量的临时寄存器时。当以与上面类似的方式编写时,GCC/clang 正是这样做的,但 Intel ICPC 将一些变量分配给堆栈上的变量并引入一些不必要的内存移动。如果相反,我以类似于以下的方式编写xmmk0xmmk6xmm0xmm7

__m128i xmmk[Rounds + 1]; // copy from input rk
// let compiler to figure out which of them are allocated on stack and which in registers,
Run Code Online (Sandbox Code Playgroud)

那么GCC/ICPC在寄存器分配方面做得很好,而clang则陷入了与之前案例中ICPC类似的情况。

当然,声明类型变量__m128i并不会使它成为寄存器,在堆栈数组中声明它也不会阻止编译器为其分配寄存器。

我能够编写一个 ASM 实现,它完全符合我的要求。然而,实际函数涉及一些指定为模板策略的编译时常量。因此,更可取的是使用 C++ 中的内在函数来实现它们。

我想知道是否有办法影响这些编译器如何执行寄存器分配。通常,由于 L1 高速缓存速度很快,性能差异仅为几个周期。然而,当高度优化时,当内部循环只需要一百个周期时,由于不必要的内存移动而导致的十几个周期差异会转化为 20% 的整体性能差异。据我所知,没有办法让编译器完全按照手写汇编的方式做事。但如果我至少能给他们一些提示,那将会很有帮助。例如,我知道加载舍入常量的延迟可以被其他指令隐藏。因此,更希望将它们分配在堆栈上而不是状态变量xmm0xmm7