如何从GCC /铿锵声组件输出中消除“噪音”?

问题描述 投票:49回答:3

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

当我编译以下示例(使用g++ -O3 -std=c++14 -S使用GCC 5.3)时,似乎编译器优化了所有内容并直接返回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
c++ gcc assembly clang
3个回答
64
投票

剥离.cfi指令,未使用的标签和注释行是一个已解决的问题:Matt Godbolt's compiler explorer背后的脚本是its github project上的开源。它甚至可以进行颜色突出显示以将源行与asm行匹配​​(使用调试信息)。

您可以在本地进行设置,这样您就可以使用所有#include路径来提供属于项目一部分的文件,依此类推(使用-I/...)。因此,您可以将它用于您不希望通过Internet发送的私有源代码。

Matt Godbolt的CppCon2017谈话“What Has My Compiler Done for Me Lately? Unbolting the Compiler's Lid”展示了如何使用它(它是非常不言自明的,但如果你阅读github上的文档有一些简洁的功能),以及如何阅读x86 asm,并为初学者简单介绍x86 asm本身,并查看编译器输出。他接着展示了一些简洁的编译器优化(例如,除以常量),以及哪种函数为查看优化的编译器输出(函数args,而不是int a = 123;)提供了有用的asm输出。


使用普通的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与x86上)。

对于C而不是C ++,使用-xc -std=gnu11或其他东西;编译器资源管理器站点只提供g ++ / clang ++,而不是gcc / clang。


有用的编译器选项,用于为人类消费制作asm:

  • 请记住,您的代码只需要编译,而不是链接:将指针传递给像void ext(int*p)这样的外部函数是阻止优化的好方法。你只需要一个原型,没有定义,所以编译器不能内联它或对它的作用做任何假设。
  • 我建议使用-O3 -Wall -Wextra -fverbose-asm -march=haswell来查看代码。 (-fverbose-asm可以让源看起来很吵,但是,当你得到的所有时间都被编号为temporaries作为操作数的名称。)当你正在摆弄源以查看它如何改变asm时,你肯定希望启用编译器警告。你不想浪费时间在asm上搔头,因为你的解释是你在源头做了一些值得警告的事情。
  • 要查看调用约定的工作原理,通常需要查看调用者和被调用者而不进行内联。 您可以在定义上使用__attribute__((noinline,noclone)) foo_t foo(bar_t x) { ... },或使用gcc -O3 -fno-inline-functions -fno-inline-functions-called-once -fno-inline-small-functions编译以禁用内联。 (但这些命令行选项不会禁用克隆常量传播的函数。)有关示例,请参阅From compiler perspective, how is reference for array dealt with, and, why passing by value(not decay) is not allowed?。 或者,如果您只想查看函数如何传递/接收不同类型的args,您可以使用不同的名称但使用相同的原型,因此编译器没有内联定义。这适用于任何编译器。
  • -ffast-math将获得许多libm函数内联,一些指令可用于单个指令(特别是SSE4可用于roundsd)。有些人只会使用-fno-math-errno-ffast-math的其他“更安全”的部分,而没有允许编译器以不同方式进行舍入的部分。如果您有FP代码,请使用/不使用-ffast-math进行查看。如果您无法在常规版本中安全地启用任何-ffast-math,也许您会在源代码中获得安全更改的想法,以便在没有-ffast-math的情况下进行相同的优化。
  • -O3 -fno-tree-vectorize将在没有自动矢量化的情况下进行优化,因此如果您想与-O2(它不能在gcc上启用自动向量化,但在clang上进行比较),您可以获得完全优化。
  • clang默认情况下展开循环,因此-funroll-loops在复杂函数中非常有用。您可以在不必浏览展开的循环的情况下了解“编译器做了什么”。 (gcc使-funroll-loops-fprofile-use,但不与-O3)。 (这是对人类可读代码的建议,而不是对运行速度更快的代码的建议。)
  • 绝对能够实现某种程度的优化,除非你特别想知道-O0做了什么。它的“可预测的调试行为”要求使编译器在每个C语句之间存储/重新加载所有内容,因此您可以使用调试器修改C变量,甚至可以“跳转”到同一函数中的不同源代码行,并继续执行,就好像您一样在C源代码中做到了这一点。 -O0输出是如此嘈杂与存储/重新加载(并且如此缓慢),不仅仅是因为缺乏优化,而是forced de-optimization to support debugging

要获得源和asm的混合,请使用gcc -Wa,-adhln -c -g foo.c | less将额外选项传递给as。 (在a blog postanother blog中对此进行了更多讨论。)。请注意,此输出不是有效的汇编程序输入,因为C源是直接存在的,而不是汇编程序注释。所以不要称之为.s。如果你想将它保存到文件中,.lst可能有意义。

Godbolt的颜色突出显示具有类似的用途,非常适合帮助您查看多个非连续的asm指令来自同一源代码行。我根本没有使用过那个gcc列表命令,所以在这种情况下,IDK的表现如何,眼睛看起来有多容易。

我喜欢godbolt的asm窗格的高代码密度,所以我认为我不想让源代码行混合在一起。至少不是简单的函数。也许有一个太复杂的功能无法处理asm的整体结构......


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

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

不要将代码放在名为main()的函数中。 gcc知道main是特殊的,并假设它只会被调用一次,因此它将其标记为“冷”并将其优化得更少。


你可以做的另一件事:如果你确实制作了main(),你可以运行它并使用调试器。 stepisi)逐步指导。有关说明,请参阅 tag wiki的底部。但请记住,在使用编译时常量args内联到main之后,代码可能会优化掉。

__attribute__((noinline))可能会对您想要不被内联的函数提供帮助。 gcc还将制作函数的常量传播克隆,即一个特殊版本,其中一个args为常量,用于知道它们传递常数的调用点。符号名称将是.clone.foo.constprop_1234或asm输出中的某些内容。您也可以使用__attribute__((noclone))来禁用它。)


例如

如果你想看看编译器如何乘以两个整数:我把以下代码on the Godbolt compiler explorer用于获取asm(来自gcc -O3 -march=haswell -fverbose-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的混合是通过将godbolt的asm输出复制粘贴到正确的位置来手工制作的。我发现这是一个很好的方式来显示一个简短的函数如何在SO答案/编译器错误报告/电子邮件中编译。)


11
投票

您始终可以从目标文件中查看生成的程序集,而不是使用编译器程序集输出。想到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选择英特尔语法而不是丑陋的AT&T语法(可选)

8
投票

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

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

我还没有遇到过这方面的问题,但asm volatile在编译器的优化器上可能非常难,因为它往往会使这些代码保持不变。

© www.soinside.com 2019 - 2024. All rights reserved.