如何从GCC / c装配件输出中消除“噪音”?


76

我想检查boost::variant在我的代码中应用的程序集输出,以查看优化了哪些中间调用。

当我编译以下示例(使用的GCC 5.3 g++ -O3 -std=c++14 -S)时,似乎编译器优化了所有内容并直接返回100:

(...)
main:
.LFB9320:
    .cfi_startproc
    movl    $100, %eax
    ret
    .cfi_endproc
(...)

#include <boost/variant.hpp>

struct Foo
{
    int get() { return 100; }
};

struct Bar
{
    int get() { return 999; }
};

using Variant = boost::variant<Foo, Bar>;


int run(Variant v)
{
    return boost::apply_visitor([](auto& x){return x.get();}, v);
}
int main()
{
    Foo f;
    return run(f);
}

但是,完整的程序集输出所包含的内容远远超过上面的摘录,在我看来,它从未被调用过。有没有办法告诉GCC / clang消除所有“噪音”,而只输出程序运行时实际调用的内容?


完整的组装输出:

    .file   "main1.cpp"
    .section    .rodata.str1.8,"aMS",@progbits,1
    .align 8
.LC0:
    .string "/opt/boost/include/boost/variant/detail/forced_return.hpp"
    .section    .rodata.str1.1,"aMS",@progbits,1
.LC1:
    .string "false"
    .section    .text.unlikely._ZN5boost6detail7variant13forced_returnIvEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIvEET_v,comdat
.LCOLDB2:
    .section    .text._ZN5boost6detail7variant13forced_returnIvEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIvEET_v,comdat
.LHOTB2:
    .p2align 4,,15
    .weak   _ZN5boost6detail7variant13forced_returnIvEET_v
    .type   _ZN5boost6detail7variant13forced_returnIvEET_v, @function
_ZN5boost6detail7variant13forced_returnIvEET_v:
.LFB1197:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    $_ZZN5boost6detail7variant13forced_returnIvEET_vE19__PRETTY_FUNCTION__, %ecx
    movl    $49, %edx
    movl    $.LC0, %esi
    movl    $.LC1, %edi
    call    __assert_fail
    .cfi_endproc
.LFE1197:
    .size   _ZN5boost6detail7variant13forced_returnIvEET_v, .-_ZN5boost6detail7variant13forced_returnIvEET_v
    .section    .text.unlikely._ZN5boost6detail7variant13forced_returnIvEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIvEET_v,comdat
.LCOLDE2:
    .section    .text._ZN5boost6detail7variant13forced_returnIvEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIvEET_v,comdat
.LHOTE2:
    .section    .text.unlikely._ZN5boost6detail7variant13forced_returnIiEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIiEET_v,comdat
.LCOLDB3:
    .section    .text._ZN5boost6detail7variant13forced_returnIiEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIiEET_v,comdat
.LHOTB3:
    .p2align 4,,15
    .weak   _ZN5boost6detail7variant13forced_returnIiEET_v
    .type   _ZN5boost6detail7variant13forced_returnIiEET_v, @function
_ZN5boost6detail7variant13forced_returnIiEET_v:
.LFB9757:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    $_ZZN5boost6detail7variant13forced_returnIiEET_vE19__PRETTY_FUNCTION__, %ecx
    movl    $39, %edx
    movl    $.LC0, %esi
    movl    $.LC1, %edi
    call    __assert_fail
    .cfi_endproc
.LFE9757:
    .size   _ZN5boost6detail7variant13forced_returnIiEET_v, .-_ZN5boost6detail7variant13forced_returnIiEET_v
    .section    .text.unlikely._ZN5boost6detail7variant13forced_returnIiEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIiEET_v,comdat
.LCOLDE3:
    .section    .text._ZN5boost6detail7variant13forced_returnIiEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIiEET_v,comdat
.LHOTE3:
    .section    .text.unlikely,"ax",@progbits
.LCOLDB4:
    .text
.LHOTB4:
    .p2align 4,,15
    .globl  _Z3runN5boost7variantI3FooJ3BarEEE
    .type   _Z3runN5boost7variantI3FooJ3BarEEE, @function
_Z3runN5boost7variantI3FooJ3BarEEE:
.LFB9310:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    (%rdi), %eax
    cltd
    xorl    %edx, %eax
    cmpl    $19, %eax
    ja  .L7
    jmp *.L9(,%rax,8)
    .section    .rodata
    .align 8
    .align 4
.L9:
    .quad   .L30
    .quad   .L10
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .quad   .L7
    .text
    .p2align 4,,10
    .p2align 3
.L7:
    call    _ZN5boost6detail7variant13forced_returnIiEET_v
    .p2align 4,,10
    .p2align 3
.L30:
    movl    $100, %eax
.L8:
    addq    $8, %rsp
    .cfi_remember_state
    .cfi_def_cfa_offset 8
    ret
    .p2align 4,,10
    .p2align 3
.L10:
    .cfi_restore_state
    movl    $999, %eax
    jmp .L8
    .cfi_endproc
.LFE9310:
    .size   _Z3runN5boost7variantI3FooJ3BarEEE, .-_Z3runN5boost7variantI3FooJ3BarEEE
    .section    .text.unlikely
.LCOLDE4:
    .text
.LHOTE4:
    .globl  _Z3runN5boost7variantI3FooI3BarEEE
    .set    _Z3runN5boost7variantI3FooI3BarEEE,_Z3runN5boost7variantI3FooJ3BarEEE
    .section    .text.unlikely
.LCOLDB5:
    .section    .text.startup,"ax",@progbits
.LHOTB5:
    .p2align 4,,15
    .globl  main
    .type   main, @function
main:
.LFB9320:
    .cfi_startproc
    movl    $100, %eax
    ret
    .cfi_endproc
.LFE9320:
    .size   main, .-main
    .section    .text.unlikely
.LCOLDE5:
    .section    .text.startup
.LHOTE5:
    .section    .rodata
    .align 32
    .type   _ZZN5boost6detail7variant13forced_returnIvEET_vE19__PRETTY_FUNCTION__, @object
    .size   _ZZN5boost6detail7variant13forced_returnIvEET_vE19__PRETTY_FUNCTION__, 58
_ZZN5boost6detail7variant13forced_returnIvEET_vE19__PRETTY_FUNCTION__:
    .string "T boost::detail::variant::forced_return() [with T = void]"
    .align 32
    .type   _ZZN5boost6detail7variant13forced_returnIiEET_vE19__PRETTY_FUNCTION__, @object
    .size   _ZZN5boost6detail7variant13forced_returnIiEET_vE19__PRETTY_FUNCTION__, 57
_ZZN5boost6detail7variant13forced_returnIiEET_vE19__PRETTY_FUNCTION__:
    .string "T boost::detail::variant::forced_return() [with T = int]"
    .ident  "GCC: (Ubuntu 5.3.0-3ubuntu1~14.04) 5.3.0 20151204"
    .section    .note.GNU-stack,"",@progbits

4
gcc不会仅仅因为没有更好的事情而生成无用的代码。为了适当地构建和链接C ++源,需要所有这些“噪音”:boost的所有行李的RTTI,等等...如果要消除所有噪音,请不要使用boost。
Sam Varshavchik,2016年

2
我相信您可以看看Godbolt如何呼叫gcc并清除残留的噪音
phuclv

2
因此,编写一个简单的Perl脚本以去除不需要的绒毛。
Sam Varshavchik '16

8
@Sam:很多标签,例如.LCOLDE3:/ .LHOTE3:,几乎都是纯噪声。我认为它们不会影响目标文件,甚至不会影响符号表或其他元数据。(是的,删除它是一个已解决的问题:godbolt.org背后的脚本是github上的开源代码)。我还要推荐 gcc.godbolt.org(带有-O3 -Wall -Wextra -march=...)用于查看代码。但是请记住,当您只想查看asm时,省去了main()并用编译时常量对其进行调用,因此您可以仅查看用于处理函数arg的代码。
彼得·科德斯

3
g++ -g -O3 -std=c++14 -c test.cc -o test.o && objdump -dS test.o
Leandros

Answers:


105

去除.cfi指令,未使用的标签和注释行是一个已解决的问题:Matt Godbolt的编译器浏览器背后的脚本是其github项目开源代码。它甚至可以进行颜色突出显示以将源代码行与asm行进行匹配(使用调试信息)。

您可以在本地设置它,以便可以使用所有#include路径将其作为项目的一部分提供给文件,以此类推(使用-I/...)。因此,您可以在不想通过Internet发送的私有源代码上使用它。

Matt Godbolt在CppCon2017上的演讲“最近我的编译器为我做了什么?“解开编译器的盖子”显示了如何使用它(这很不言自明,但是如果您阅读github上的文档,它会具有一些简洁的功能),以及如何阅读x86 asm,并对初学者全面介绍了x86 asm本身,并查看编译器输出。他继续展示了一些巧妙的编译器优化(例如,用于除以常数),以及什么样的函数为查看优化的编译器输出提供了有用的asm输出(函数args,不是int a = 123;)。


对于普通的gcc / clang(不是g ++),-fno-asynchronous-unwind-tables避免使用.cfi指令。可能也有用:-fno-exceptions -fno-rtti -masm=intel。确保省略-g

复制/粘贴此内容以供本地使用

g++ -fno-asynchronous-unwind-tables -fno-exceptions -fno-rtti -fverbose-asm \
    -Wall -Wextra  foo.cpp   -O3 -masm=intel -S -o- | less

但实际上,我建议直接使用Godbolt(在线或在本地设置)!您可以在gcc和clang版本之间快速切换,以查看旧的或新的编译器是否做了一些愚蠢的事情。(或者ICC做什么,甚至MSVC做什么。)甚至还有ARM / ARM64 gcc 6.3,以及用于PowerPC,MIPS,AVR,MSP430的各种gcc。(有趣的是,在int比寄存器宽或不是32位的机器上发生了什么,或者在RISC vs. x86上发生了什么)。

对于C而不是C ++,请使用-xc -std=gnu11或之类的东西。编译器资源管理器站点仅提供g ++ / clang ++,而不提供gcc / clang。(或者,您可以在语言下拉列表中使用C模式,但是选择的编译器有所不同,但大多数情况下都受到限制。它会重置源窗格,因此在C和C ++之间切换更像是一种折磨。)


制作供人消费的asm的有用编译器选项

  • 请记住,您的代码仅需编译,而无需链接:将指针传递给外部函数之类void ext(int*p)是阻止某些东西远离优化的好方法。您只需要一个没有定义的原型,因此编译器无法对其进行内联或对其功能进行任何假设。

  • 我建议使用-O3 -Wall -Wextra -fverbose-asm -march=haswell)查看代码。(-fverbose-asm不过,当所有获得的临时名称都被编号为操作数时,可以使源看起来很吵。)当您摆弄源代码以查看它如何更改asm时,您肯定希望启用编译器警告。当您的解释是您做了一些值得在源代码中警告的事情时,您不想浪费时间在asm上抓挠头。

  • 要查看调用约定的工作原理,您通常希望查看没有内联的caller和被调用方

    您可以__attribute__((noinline,noclone)) foo_t foo(bar_t x) { ... }在定义中使用,也可以使用进行编译gcc -O3 -fno-inline-functions -fno-inline-functions-called-once -fno-inline-small-functions以禁用内联。(但是这些命令行选项不会禁用为常数传播而克隆功能。)从编译器的角度来看,如何处理数组引用,以及为什么不允许按值传递(不衰减)?举个例子

    或者,如果您只想查看函数如何传递/接收不同类型的args,则可以使用不同的名称,但使用相同的原型,因此编译器没有内联的定义。这适用于任何编译器。

  • -ffast-math将获得许多libm函数以内联,其中一些函数可嵌入一条指令(尤其是可用于的SSE4 roundsd)。有些将内联的just-fno-math-errno或其他“更安全”的部分-ffast-math,而没有允许编译器以不同方式取整的部分。如果您有FP代码,则一定要带/不带FP代码-ffast-math。如果您不能安全地-ffast-math在常规版本中启用任何功能,则可能会想到一个安全的更改,您可以在源代码中进行更改,以允许不使用即可进行相同的优化-ffast-math

  • -O3 -fno-tree-vectorize会在不进行自动向量化的情况下进行优化,因此无需进行比较就可以进行全面优化-O2(这不会在gcc上启用自动向量化,但在clang上会启用自动向量化)。

  • clang默认情况下会展开循环,因此-fno-unroll-loops在复杂函数中很有用。您无需经过展开的循环就可以了解“编译器做了什么”。(GCC能够-funroll-loops-fprofile-use,但不是-O3)。(这是对人类可读代码的建议,而不是对运行速度更快的代码的建议。)

  • 绝对能够优化一定程度上,除非您特别想知道什么-O0。它的“可预测的调试行为”要求使编译器可以在每个C语句之间存储/重新加载所有内容,因此您可以使用调试器修改C变量,甚至可以“跳转”到同一函数中的其他源代码行,并像您一样继续执行在C源代码中做到了。 -O0存储/重装的输出是如此嘈杂(并且如此之慢),这不仅是因为缺乏优化,而且还因为强制优化而无法支持调试。(也相关)。


要混合使用source和asm,请使用gcc -Wa,-adhln -c -g foo.c | less传递额外的选项到as。(有关详细信息,请参见博客文章其他博客。)。请注意,此输出不是有效的汇编程序输入,因为C源代码直接存在于此,而不是作为汇编程序注释。所以不要称它为.s。一.lst,如果你想将它保存到一个文件可能是有意义的。

Godbolt的颜色突出显示具有类似的目的,并且可以帮助您查看何时来自同一源代码行的多个不连续的asm指令。我根本没有使用过该gcc列出命令,因此在这种情况下,IDK的效果如何,以及眼睛的视线有多容易。

我喜欢Godbolt的asm窗格的高代码密度,所以我不希望混合源代码行。至少对于简单的函数而言,这不是。也许它的功能过于复杂,无法掌握asm的整体结构...


记住,当您只想查看asm时,请忽略main()和编译时常量。您想查看用于处理寄存器中arg函数的代码,而不是用于在常量传播将其转换为return 42或至少优化了某些东西之后的代码。

从函数中删除static和/或inline从函数中删除将为它们生成一个独立的定义,以及为任何调用者创建的定义,因此您可以看一下。

请勿将您的代码放入名为的函数中main()。gcc知道这main很特殊,并假定它只会被调用一次,因此将其标记为“冷”并对其进行了优化。


您可以做的另一件事:如果制作了main(),则可以运行它并使用调试器。 stepisi)按指示进行。见底部 标记维基以获取指示。但是请记住,在使用编译时常数args内联到main之后,代码可能会进行优化。

__attribute__((noinline))在您不希望内联的功能上可能会有所帮助。gcc还将对函数进行常量传播的克隆,即以一个args作为常量的特殊版本,用于知道它们正在传递常量的调用站点。符号名称.clone.foo.constprop_1234在asm输出中为或。您也可以__attribute__((noclone))禁用它。)。


例如

如果要查看编译器如何将两个整数相乘:我将以下代码放在Godbolt编译器资源管理器上gcc -O3 -march=haswell -fverbose-asm,以错误的方式和正确的方式来测试(从中获得)asm 。

// the wrong way, which people often write when they're used to creating a runnable test-case with a main() and a printf
// or worse, people will actually look at the asm for such a main()
int constants() { int a = 10, b = 20; return a * b; }
    mov     eax, 200  #,
    ret                     # compiles the same as  return 200;  not interesting

// the right way: compiler doesn't know anything about the inputs
// so we get asm like what would happen when this inlines into a bigger function.
int variables(int a, int b) { return a * b; }
    mov     eax, edi  # D.2345, a
    imul    eax, esi        # D.2345, b
    ret

(这种asm和C的混合是手工制作的,方法是将gosbolt的asm输出复制粘贴到正确的位置。我发现这是显示短函数如何在SO答案/编译器错误报告/电子邮件中进行编译的好方法。)


4
您可以通过禁用CFI指令-fno-asynchronous-unwind-tables
edmz '16

15

您始终可以从目标文件查看生成的程序集,而不是使用编译器程序集输出。objdump浮现在脑海。

您甚至可以告诉objdump将源代码与程序集混合在一起,从而更容易弄清楚哪些源代码行与哪些指令相对应。会话示例:

$ cat test.cc
int foo(int arg)
{
    return arg * 42;
}

$ g++ -g -O3 -std=c++14 -c test.cc -o test.o && objdump -dS -M intel test.o

test.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <_Z3fooi>:
int foo(int arg)
{
    return arg + 1;
   0:   8d 47 01                lea    eax,[rdi+0x1]
}
   3:   c3                      ret    

objdump标志说明:

  • -d 分解所有可执行部分
  • -S将程序集与源混合(-g使用时需要g++
  • -M intel选择intel语法而不是难看的AT&T语法(可选

4
我喜欢objdump -Mintel -drw在分解.o文件时显示重定位(-r)的符号名称,而不是对多字节指令的机器代码进行换行。有时这更易读,但由于您会丢失分支目标和其他一些信息上的标签,因此可读性会大大降低。(Agner Fog的objconv反汇编程序将为反汇编中的分支目标创建标签)。这也意味着您无法从中受益gcc -fverbose-asm
彼得·科德斯

1
当让GCC生成装配清单时,是否没有将装配与源混合的选项?MSVC有此选项(还有其他几个非常方便)。一旦滚动到顶部的模板和标准库goo,生成的代码实际上非常清晰易读。
科迪·格雷

1
@CodyGray:事实证明,可以使用GNU选项as。查看我的答案的更新。不过,当我想知道这一点时,我通常只是看着启用了颜色突出显示的Godbolt。与源代码行关联的asm指令并不总是连续的...
Peter Cordes

1
脚本GNU转换-Mintel语法更精确NASM语法,如更换byte ptrbyte,也许一对夫妇的其他小东西。
彼得·科德斯

1
@PeterCordes该脚本是有害的,因为它不仅不完整,而且还剥离了重要的信息,请参阅我对该帖子的评论。
Ruslan

10

我喜欢插入标签,以便可以从objdump输出中轻松grep。

int main() {
    asm volatile ("interesting_part_begin%=:":);
    do_something();
    asm volatile ("interesting_part_end%=:":);
}

我还没有遇到任何问题,但是asm volatile在编译器的优化器上可能会很难,因为它倾向于使此类代码保持不变。


确定要volatile在这里吗?您是否注意到编译器在没有标签的情况下优化了这些标签?
科迪·格雷

@CodyGray我还没有尝试过。我认为您会需要,volatile因为优化程序会在DCE期间将其丢弃。
蒂姆(Tim)

1
您实际上并不需要volatile,因为GCC会假定它是因为asm语句没有输出操作数。
Ross Ridge)

4
asm没有输出操作数的asm goto语句(包括语句)是隐式易失的。” gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#Volatile
Ross Ridge)

1
您可以使用asm注释(asm("#start of something")),但是在这种情况下,发出甚至无法汇编的asm可能是一个好主意。这样可以确保您不会意外地将其留在实际的构建中并简化优化程序。另外,我喜欢您使用扩展asm语法在语句的每个克隆上放置唯一ID的想法,这是帮助查找对函数的多次调用的好主意。不过,我不确定是否会volatile停止优化程序的克隆。也许可以克隆它,只要它仍然运行正确的次数即可。
彼得·科德斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.