GCC 生成不需要的汇编代码

Posted

技术标签:

【中文标题】GCC 生成不需要的汇编代码【英文标题】:GCC generates undesired assembly code 【发布时间】:2015-10-08 19:26:28 【问题描述】:

我正在研究矢量化循环,而 GCC 让我很难过。 当我查看它生成的汇编代码时,我看到了很多我想去掉的奇怪的行。

例如,通过矢量化,我了解到您可以通过向 GCC 提供有关数组对齐的附加信息来避免大量额外的装配线。 http://locklessinc.com/articles/vectorize/

这是我的实验。

#define SIZE 1024
void itwillwork (const uint16_t * a, const  uint16_t * b, uint16_t * comp) 
    int i = 0;
    comp[i]=a[i]|b[i];

生成简单的程序集:

.globl  _ZN8Test_LUT7performEv
  23                _ZN8Test_LUT7performEv:
  24                .LFB664:
  25                    .cfi_startproc
  26 0020 488B4710      movq    16(%rdi), %rax
  27 0024 488B4F08      movq    8(%rdi), %rcx
  28 0028 488B5720      movq    32(%rdi), %rdx
  29 002c 0FB700        movzwl  (%rax), %eax
  30 002f 660B01        orw (%rcx), %ax
  31 0032 668902        movw    %ax, (%rdx)
  32 0035 C3            ret
  33                    .cfi_endproc

但是,即使我期待一些额外的行,我对添加循环后得到的结果感到非常惊讶:

#define SIZE 1024
void itwillwork (const uint16_t * a, const  uint16_t * b, uint16_t * comp) 
    int i = 0;
    for(i=0;i<SIZE;i++)
        comp[i]=a[i]|b[i];

用更多的行生成这个程序集:

 233                _Z10itwillworkPKtS0_Pt:
 234                .LFB663:
 235                    .cfi_startproc
 236 0250 488D4210      leaq    16(%rdx), %rax
 237 0254 488D4E10      leaq    16(%rsi), %rcx
 238 0258 4839F0        cmpq    %rsi, %rax
 239 025b 410F96C0      setbe   %r8b
 240 025f 4839CA        cmpq    %rcx, %rdx
 241 0262 0F93C1        setnb   %cl
 242 0265 4108C8        orb %cl, %r8b
 243 0268 743E          je  .L55
 244 026a 4839F8        cmpq    %rdi, %rax
 245 026d 488D4710      leaq    16(%rdi), %rax
 246 0271 0F96C1        setbe   %cl
 247 0274 4839C2        cmpq    %rax, %rdx
 248 0277 0F93C0        setnb   %al
 249 027a 08C1          orb %al, %cl
 250 027c 742A          je  .L55
 251 027e 31C0          xorl    %eax, %eax
 252                    .p2align 4,,10
 253                    .p2align 3
 254                .L57:
 255 0280 F30F6F0C      movdqu  (%rsi,%rax), %xmm1
 255      06
 256 0285 F30F6F04      movdqu  (%rdi,%rax), %xmm0
 256      07
 257 028a 660FEBC1      por %xmm1, %xmm0
 258 028e F30F7F04      movdqu  %xmm0, (%rdx,%rax)
 258      02
 259 0293 4883C010      addq    $16, %rax
 260 0297 483D0008      cmpq    $2048, %rax
 260      0000
 261 029d 75E1          jne .L57
 262 029f F3C3          rep ret
 263                    .p2align 4,,10
 264 02a1 0F1F8000      .p2align 3
 264      000000
 265                .L55:
 266 02a8 31C0          xorl    %eax, %eax
 267 02aa 660F1F44      .p2align 4,,10
 267      0000
 268                    .p2align 3
 269                .L58:
 270 02b0 0FB70C06      movzwl  (%rsi,%rax), %ecx
 271 02b4 660B0C07      orw (%rdi,%rax), %cx
 272 02b8 66890C02      movw    %cx, (%rdx,%rax)
 273 02bc 4883C002      addq    $2, %rax
 274 02c0 483D0008      cmpq    $2048, %rax
 274      0000
 275 02c6 75E8          jne .L58
 276 02c8 F3C3          rep ret
 277                    .cfi_endproc

两者都是使用 gcc 4.8.4 在发布模式下编译的,-O2 -ftree-vectorize -msse2。

有人可以帮我摆脱这些线条吗?或者,如果不可能,你能告诉我他们为什么在那里吗?

更新:

我已经尝试过 http://locklessinc.com/articles/vectorize/ 那里的技巧,但我遇到了另一个问题:

#define SIZE 1024
void itwillwork (const uint16_t * a, const  uint16_t * b, uint16_t * comp) 
    int i = 0;
    for(i=0;i<SIZE;i++)
        comp[i]=a[i]|b[i];

为此功能生成了几条装配线,我明白了。 但是当我从其他地方调用这个函数时:

itwillwork(a,b,c);

没有调用指令:直接使用“itwillwork”的一长串指令(同上)。 我错过了什么吗? (“额外的行”是问题,而不是内联调用)

【问题讨论】:

一个好主意是将汇编代码粘贴到您的问题中,而不必去 pastebin 站点。我不明白您为什么在问题中放入高级代码,而不是程序集。 为什么你认为有这么多行是一件坏事?如果优化后的代码运行速度很快,你是否关心代码大小?而且,你对restrict 做了什么尝试(发布那个版本的 C++ 代码)。在我看来,asm 代码就像编译器不知道限制指针(尤其是comp),如果是的话,效率会更高。 我试图在这里粘贴汇编代码,但它根本不可读(由于某种原因,没有换行)。 编译器没有调用您的代码,因为它已经确定代码足够小(且简单)可以内联。 编译器为这个函数产生大量代码的原因主要来自两个问题:1.指针别名,2.对齐。您可以向编译器保证您不会使用 restrict 为您的指针起别名,并且 gcc 有一个内置函数可以保证指针与 __buitin_assume_aligned() 对齐。 【参考方案1】:

你得到 "weird" 代码是因为 GCC 不能对你的指针的对齐做出假设,所以你可以看到它首先执行对齐测试以确定它是否可以走快速路径和一次执行 128 位,或者慢速路径一次执行 16 位。

此外,您发现代码重复的原因是编译器正在应用内联优化。您可以使用 __attribute((noinline)) 规范禁用此功能,但如果您的目标是性能,请让编译器内联它。

如果您指定__restrict 关键字,那么编译器将只生成快速路径代码:https://goo.gl/g3jUfQ

但是,这并不意味着编译器会神奇地为您处理对齐问题,因此请注意传递给函数的内容。

【讨论】:

我试过了,效果很好,谢谢!然而,我尝试使用一个类(变量现在是属性,函数现在是方法,请参阅goo.gl/x7aP6W - 代码不应该做任何有趣的事情)。如您所见,这是“正确”编译的。但是,一旦我将此代码拆分为“.h”和“.cpp”文件,我就会得到前面提到的额外行。知道为什么吗? 原型和定义是什么样子的。

以上是关于GCC 生成不需要的汇编代码的主要内容,如果未能解决你的问题,请参考以下文章

gcc延时绑定参数

gcc/g++编译(生动形象,从最容易入手的hello world解释了库的概念)

mac下的gcc如何使用。。

gcc编译器的使用方法

如何使用 gcc 生成 Intel 语法的汇编代码?

GCC生成的汇编代码