如何让GCC完全展开这个循环(即剥离这个循环)?

李哲源*_*李哲源 9 c x86 gcc hpc loop-unrolling

有没有一种方法来指示GCC(版本我用4.8.4),以展开在底层函数while循环完全,即剥离这个循环?循环的迭代次数在编译时是已知的:58.

我先解释一下我的尝试.

通过检查GAS输出:

gcc -fpic -O2 -S GEPDOT.c
Run Code Online (Sandbox Code Playgroud)

使用12个寄存器XMM0 - XMM11.如果我将标志-funroll-loops传递给gcc:

gcc -fpic -O2 -funroll-loops -S GEPDOT.c
Run Code Online (Sandbox Code Playgroud)

循环只展开两次.我检查了GCC优化选项.GCC表示-funroll-loops也会打开-frename-registers,所以当GCC展开一个循环时,它先前选择的寄存器分配是使用"遗留"寄存器.但是XMM12只剩下4个 - XMM15,所以GCC最多只能展开2次.如果有48个而不是16个XMM寄存器可供使用,GCC将毫无困难地展开while循环4次.

然而,我做了另一个实验.我首先手动两次展开while循环,获得一个函数GEPDOT_2.然后两者之间没有任何区别

gcc -fpic -O2 -S GEPDOT_2.c
Run Code Online (Sandbox Code Playgroud)

gcc -fpic -O2 -funroll-loops -S GEPDOT_2.c
Run Code Online (Sandbox Code Playgroud)

由于GEPDOT_2已用完所有寄存器,因此不执行展开.

GCC确实注册了重命名,以避免引入潜在的错误依赖.但我确信在我的GEPDOT中没有这样的潜力; 即使有,也不重要.我尝试自己展开循环,展开4次比展开2次更快,比没有展开更快.当然我可以手动展开更多次,但这很乏味.GCC可以帮我吗?谢谢.

// C file "GEPDOT.c"
#include <emmintrin.h>

void GEPDOT (double *A, double *B, double *C) {
  __m128d A1_vec = _mm_load_pd(A); A += 2;
  __m128d B_vec = _mm_load1_pd(B); B++;
  __m128d C1_vec = A1_vec * B_vec;
  __m128d A2_vec = _mm_load_pd(A); A += 2;
  __m128d C2_vec = A2_vec * B_vec;
  B_vec = _mm_load1_pd(B); B++;
  __m128d C3_vec = A1_vec * B_vec;
  __m128d C4_vec = A2_vec * B_vec;
  B_vec = _mm_load1_pd(B); B++;
  __m128d C5_vec = A1_vec * B_vec;
  __m128d C6_vec = A2_vec * B_vec;
  B_vec = _mm_load1_pd(B); B++;
  __m128d C7_vec = A1_vec * B_vec;
  A1_vec = _mm_load_pd(A); A += 2;
  __m128d C8_vec = A2_vec * B_vec;
  B_vec = _mm_load1_pd(B); B++;
  int k = 58;
  /* can compiler unroll the loop completely (i.e., peel this loop)? */
  while (k--) {
    C1_vec += A1_vec * B_vec;
    A2_vec = _mm_load_pd(A); A += 2;
    C2_vec += A2_vec * B_vec;
    B_vec = _mm_load1_pd(B); B++;
    C3_vec += A1_vec * B_vec;
    C4_vec += A2_vec * B_vec;
    B_vec = _mm_load1_pd(B); B++;
    C5_vec += A1_vec * B_vec;
    C6_vec += A2_vec * B_vec;
    B_vec = _mm_load1_pd(B); B++;
    C7_vec += A1_vec * B_vec;
    A1_vec = _mm_load_pd(A); A += 2;
    C8_vec += A2_vec * B_vec;
    B_vec = _mm_load1_pd(B); B++;
    }
  C1_vec += A1_vec * B_vec;
  A2_vec = _mm_load_pd(A);
  C2_vec += A2_vec * B_vec;
  B_vec = _mm_load1_pd(B); B++;
  C3_vec += A1_vec * B_vec;
  C4_vec += A2_vec * B_vec;
  B_vec = _mm_load1_pd(B); B++;
  C5_vec += A1_vec * B_vec;
  C6_vec += A2_vec * B_vec;
  B_vec = _mm_load1_pd(B);
  C7_vec += A1_vec * B_vec;
  C8_vec += A2_vec * B_vec;
  /* [write-back] */
  A1_vec = _mm_load_pd(C); C1_vec = A1_vec - C1_vec;
  A2_vec = _mm_load_pd(C + 2); C2_vec = A2_vec - C2_vec;
  A1_vec = _mm_load_pd(C + 4); C3_vec = A1_vec - C3_vec;
  A2_vec = _mm_load_pd(C + 6); C4_vec = A2_vec - C4_vec;
  A1_vec = _mm_load_pd(C + 8); C5_vec = A1_vec - C5_vec;
  A2_vec = _mm_load_pd(C + 10); C6_vec = A2_vec - C6_vec;
  A1_vec = _mm_load_pd(C + 12); C7_vec = A1_vec - C7_vec;
  A2_vec = _mm_load_pd(C + 14); C8_vec = A2_vec - C8_vec;
  _mm_store_pd(C,C1_vec); _mm_store_pd(C + 2,C2_vec);
  _mm_store_pd(C + 4,C3_vec); _mm_store_pd(C + 6,C4_vec);
  _mm_store_pd(C + 8,C5_vec); _mm_store_pd(C + 10,C6_vec);
  _mm_store_pd(C + 12,C7_vec); _mm_store_pd(C + 14,C8_vec);
  }
Run Code Online (Sandbox Code Playgroud)

更新1

感谢@ user3386109的评论,我想稍微扩展这个问题.@ user3386109提出了一个非常好的问题.实际上,当有很多并行指令需要调度时,我确实对编译器的最佳寄存器分配能力有所怀疑.

我个人认为一种可靠的方法是首先在asm内联汇编中编码循环体(这是HPC的关键),然后根据需要复制它多次.今年早些时候,我有一个不受欢迎的帖子:内联汇编.代码有点不同,因为循环迭代次数j是一个函数参数,因此在编译时是未知的.在这种情况下,我无法完全展开循环,因此我只复制了汇编代码两次,并将循环转换为标签并跳转.事实证明,我编写的程序集的最终性能比编译器生成的程序集高约5%,这可能表明编译器无法以我们预期的最​​佳方式分配寄存器.

我(并且仍然)是汇编编码的宝贝,所以这对我来说是一个很好的案例研究,可以学习x86汇编.但从长远来看,我并不倾向于对GEPDOT进行大规模的装配编码.主要有三个原因:

  1. asm内联汇编因不可移植而受到批评.虽然我不明白为什么.也许是因为不同的机器有不同的寄存器?
  2. 编译器也越来越好.所以我仍然更喜欢算法优化和更好的C编码习惯,以帮助编译器产生良好的输出;
  3. 最后一个原因更为重要.迭代次数可能并不总是58.我正在开发一个高性能矩阵分解子程序.对于高速缓存阻塞因子nb,迭代次数将是(nb-2).我不打算将nb作为函数参数,就像我在之前的帖子中所做的那样.这是一个特定于机器的参数,将被定义为宏.因此,迭代次数在编译时已知,但可能因机器而异.猜猜在手动循环展开各种nb时我需要做多少乏味的工作.因此,如果有一种方法可以简单地指示编译器剥离循环,那就太好了.

如果您还可以分享一些生产高性能,便携式库的经验,我将不胜感激.

fuz*_*fuz 3

尝试调整优化器参数:

gcc -O3 -funroll-loops --param max-completely-peeled-insns=1000 --param max-completely-peel-times=100
Run Code Online (Sandbox Code Playgroud)

这应该可以解决问题。