是否可能影响GCC/Clang/Intel ICPC XMM/YMM寄存器分配?
我有一个高度优化的函数,在内部循环中反复调用,使用SSE2/AVX2加速编写。经过一些改进,现在我接近理论上的最佳性能(基于指令延迟和吞吐量)。但是,性能不是很好移植。问题在于有16个以上的是否可能影响GCC/Clang/Intel ICPC XMM/YMM寄存器分配?,gcc,assembly,clang,intrinsics,icc,Gcc,Assembly,Clang,Intrinsics,Icc,我有一个高度优化的函数,在内部循环中反复调用,使用SSE2/AVX2加速编写。经过一些改进,现在我接近理论上的最佳性能(基于指令延迟和吞吐量)。但是,性能不是很好移植。问题在于有16个以上的\uuuM128i/\uuuu256i变量。当然,其中只有16个可以在寄存器中分配,其余的可以在堆栈中分配。函数大致如下所示: void eval(size_t n, __m128i *rk /* other inputs */) { __m128i xmmk0 = rk[0]; // ...
\uuuM128i
/\uuuu256i
变量。当然,其中只有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]
}
}
涉及的变量超过16个。我能获得的最佳性能是,编译器将xmm0
分配给xmm7
,将xmmk0
分配给xmmk6
,将前7个循环常量分配到寄存器中,并将剩余的寄存器用作临时寄存器,为剩余的循环加载循环常量。当以与上述类似的方式编写时,GCC/clang正是这样做的,但Intel ICPC将一些xmm0
分配给堆栈上的xmm7
变量,并引入一些不必要的内存移动。如果相反,我写的方式与下面类似
__m128i xmmk[Rounds + 1]; // copy from input rk
// let compiler to figure out which of them are allocated on stack and which in registers,
然后GCC/ICPC在寄存器分配方面做得很好,而clang陷入了类似于前一个案例中ICPC的情况
当然,声明\uuu m128i
类型的变量不会使其成为寄存器,在堆栈数组中声明也不会阻止编译器为其分配寄存器
我能够编写一个ASM实现,它完全符合我的要求。但是,实际函数涉及一些指定为模板策略的编译时常量。因此,在C++中用内联实现它们是更可取的。p>
我想知道的是,是否有办法影响这些编译器执行寄存器分配的方式。由于一级缓存速度快,通常性能上的差异只是几个周期。但是,当高度优化时,当内部循环只需要100个周期时,由于不必要的内存移动而产生的十几个周期差异将转化为20%的总体性能差异。据我所知,没有办法让编译器完全像手工编写的汇编一样工作。但如果我至少能给他们一些提示,那将是非常有帮助的。例如,我知道加载round常量的延迟可以被其他指令隐藏。因此,更希望在堆栈上分配它们,而不是将状态变量
xmm0
分配给xmm7
您是否尝试过注册
关键字?:)是的,这只是一个提示,编译器可能会忽略它。@Jester这是我第一次尝试的。我已经好几年没用过那个关键词了。但它根本不起作用,正如预期的那样。事实上,据我所知,这个寄存器将在C++1z中删除。如果地址是常量,那么可能不是延迟(OOO隐藏的延迟),而是额外的UOP。您甚至可以尝试类似于register\uuuu m128i foo asm(“xmm0”)代码>。GNU扩展强制编译器在整个函数中为该变量使用该寄存器,这实际上可能会使情况变得更糟,因为在非交换操作结束后,需要MOVDQA将结果放回该寄存器,这样会更自然地将变量的当前值放在另一个寄存器中。我只是尝试过,在简单的情况下,它似乎并没有打败优化器:().@兖州,根据你使用的语言来标记C或C++可能是有用的。是的,更多的人看<代码> [C] < /C>标签,而不是<代码> [GCC ] < /C>或<代码> [汇编] < /Cord>。我可能会丢掉“内在论”的标签。