[解決済み] GCC/clangのアセンブリ出力から "ノイズ "を取り除くには?
質問
を適用した場合のアセンブリ出力を検査したいのですが。
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にそのすべての"noise"を取り除き、プログラムが実行されたときに実際に呼び出されたものだけを出力するように指示する方法はありますか?
フルアセンブリの出力です。
.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
どのように解決するのですか?
を削除して
.cfi
ディレクティブ、使われていないラベル、コメント行を取り除くことは、解決された問題です。
Matt Godbolt のコンパイラエクスプローラ
はオープンソースで
github プロジェクト
. デバッグ情報を使って、ソース行とasm行を一致させるカラーハイライトも可能です。
ローカルに設定することで、プロジェクトの一部であるファイルに、すべての
#include
のパスなどを含むプロジェクトのファイルを与えることができます (これは
-I/...
). そのため、インターネット上で送信したくないプライベートなソースコードに使用することができます。
マット・ゴッドボルトのCppCon2017の講演内容
"最近、私のコンパイラは私のために何をしてくれたのか?コンパイラの蓋を開ける"
は、その使い方を示しています。
(かなり自明ですが、githubのドキュメントを読めばいくつかの巧妙な機能があります)、そしてまた
x86 asmの読み方
で、全くの初心者のためにx86 asmそのものと、コンパイラの出力の見方について優しく紹介しています。 さらに、コンパイラの最適化(例えば定数で割る)や、最適化されたコンパイラの出力を見るために、どのような関数が有用なasmの出力を与えるか(関数の引数、ではなく
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 のバージョンをすばやく切り替えて、古いコンパイラーや新しいコンパイラーが何かおかしなことをしていないかどうかを確認することができます。 (ARM / ARM64 gcc 6.3や、PowerPC, MIPS, AVR, MSP430用の様々なgccがあります。 (以下のようなマシンで何が起こるかを見るのは面白いかもしれません。
int
がレジスタより広い、あるいは 32 ビットでないマシンで何が起こるかを見るのは興味深いことです。 または、RISC と x86 の比較)。
C++ の代わりに C を使用する場合は
-xc -std=gnu11
コンパイラエクスプローラのサイトでは gcc / clang ではなく g++ / clang++ しか提供されていません。 (または、言語ドロップダウンで C モードを使用することもできますが、その場合はコンパイラの選択が異なり、ほとんどがより限定されたものになります。 また、ソース ペインがリセットされるため、C と C++ の間を切り替えるのはより困難です)。
人間が食べるためのasmを作るのに便利なコンパイラのオプション :
-
あなたのコードはコンパイルするだけで、リンクする必要はないことを忘れないでください。
void ext(void*p)
のような外部関数へのポインタを渡すことは、最適化から何かを止めるための良い方法です。 . あなたは、コンパイラがそれをインライン化したり、それが何をするかについて仮定することができないように、定義のない、それのためのプロトタイプを必要とするだけです。 (あるいは のような asm をインライン化することもできます。Benchmark::DoNotOptimize
はコンパイラにレジスタの値を実体化させたり、既知の定数であることを忘れさせることができます。もしあなたがGNU C inline asm構文を十分に知っていて、制約を使ってコンパイラに要求していることの効果を理解できるのであれば、です)。 -
を使うことをお勧めします。
-O3 -Wall -Wextra -fverbose-asm -march=haswell
) を使ってコードを見ることをお勧めします。 (-fverbose-asm
は、オペランドの名前として番号付きのテンポラリしか得られない場合、ソースをただ騒がしく見せるだけです)。 ソースをいじってasmがどのように変化するかを見る場合、あなたは 間違いなく コンパイラの警告を有効にしたい。 ソースで警告に値することをしたという説明なのに、asmの上で頭をかきむしって時間を無駄にしたくないでしょう。 -
呼び出しの規約がどのように機能するかを見るために をインライン化せずに呼び出し側と呼び出され側を見たいと思うことがよくあります。 .
を使うことができます。
__attribute__((noipa)) foo_t foo(bar_t x) { ... }
を使ってコンパイルするか、あるいはgcc -O3 -fno-inline-functions -fno-inline-functions-called-once -fno-inline-small-functions
を付けてコンパイルすると、インライン化が無効になります。 (しかし、これらのコマンドラインオプションは定数伝播のための関数のクローンを無効にするものではありません。noipa
= Inter-Procedural Analysisを無効にします。 よりもさらに強く__attribute__((noinline,noclone))
.) 参照 コンパイラの観点からは、配列の参照はどのように扱われるのでしょうか? を参照してください。また、関数がどのように異なる型の引数を渡したり受け取ったりするかを見たいだけなら、コンパイラがインライン化する定義を持っていないように、異なる名前だが同じプロトタイプを使用することができます。 これはどのコンパイラーでも使えます。 定義がなければ、関数はオプティマイザーにとって単なるブラックボックスであり、呼び出しの規則やABIによってのみ支配されます。
-
-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はデフォルトでループをアンロールするので
-fno-unroll-loops
は複雑な関数で有用です . ループを展開することなく、コンパイラが何をしたかを知ることができます。 (gccでは-funroll-loops
で-fprofile-use
とは異なり-O3
). (これは人間が読みやすいコードのための提案であり、より速く実行されるコードのためのものではありません)。 -
を特に知りたいのでなければ、間違いなく、ある程度の最適化を可能にします。
-O0
が行いました。 . その "predictable debug behaviour" の要件により、コンパイラーはすべての C ステートメント間ですべてを保存/再ロードするため、デバッガーで C 変数を変更したり、同じ関数内で別のソース行に "jump" して、C ソース内でそれを行ったかのように実行を継続することができます。-O0
の出力がストアやリロードでうるさい (そして遅い) のは、最適化がされていないだけでなく デバッグをサポートするために強制的に最適化を解除しています。 . (また 関連 ).
ソースとasmの混在を取得するために
を使用します。
gcc -Wa,-adhln -c -g foo.c | less
に追加オプションを渡すために
as
. (これについての詳しい説明は
ブログ記事
で、そして
別のブログ
.). C のソースはアセンブラのコメントとしてではなく、直接そこにあるので、この出力は有効なアセンブラの入力ではないことに注意してください。 ですから、これを
.s
. A
.lst
は、それをファイルに保存したい場合に意味をなすかもしれません。
Godbolt のカラーハイライトも同じような目的で使用され、複数の 非連続 asm 命令が同じソース ラインから来たときに、それを見分けるのに役立ちます。 私はその gcc リスト コマンドをまったく使用したことがないので、その場合、それがどの程度うまく機能し、目にとってどの程度簡単であるかは IDK です。
私は godbolt の asm ペインの高いコード密度が好きなので、ソース行が混在しているのは好ましくないと思っています。 少なくとも、単純な関数についてはそうではありません。 多分、asm が行うことの全体的な構造を把握するには複雑すぎる機能では...。
そして、asmを見るだけでいいという時に思い出してください。
を省き
main()
とコンパイル時の定数
. あなたが見たいのは、レジスタ内の関数argを扱うコードであって、定数伝搬で
return 42
に変えた後のコードではなく、少なくともいくつかのものを最適化した後のコードを見たいのです。
削除する
static
または
inline
は、関数から独立した定義を生成し、呼び出し元の定義も生成するので、それを見るだけです。
という関数にコードを書かないようにしましょう。
main()
gcc は
main
は特別なもので、一度だけ呼ばれると仮定しているので、 "cold"としてマークし、あまり最適化しないようにします。
もうひとつ、できることがあります。もし、あなたが
main()
を作った場合、それを実行してデバッガを使うことができます。
stepi
(
si
) のステップをインストラクションごとに説明します。 の下を参照してください。
x86
タグの wiki
を参照してください。 しかし、コンパイル時定数の引数でmainにインライン化した後、コードが最適化されなくなる可能性があることを忘れないでください。
__attribute__((noinline))
は、インライン化されないようにしたい関数で、役に立つかもしれません。 gccはまた、定数を渡していることを知っている呼び出し元に対して、定数伝播型の関数のクローン、つまり、引数の1つを定数とした特別なバージョンを作成します。 シンボル名は
.clone.foo.constprop_1234
などと出力されます。あなたは
__attribute__((noclone))
を使ってそれを無効にすることもできます)。
例えば
コンパイラがどのように2つの整数を乗算しているかを見たい場合。 次のようなコードを書いてみました。
をGodboltコンパイラエクスプローラの
を実行して、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の回答やコンパイラのバグレポート、メールなどで短い関数がどのようにコンパイルされるかを示すには良い方法だと思います)。
関連
-
[解決済み】C++でint型に無限大を設定する
-
[解決済み】識別子 "string "は未定義?
-
[解決済み】C++ 式はポインタからオブジェクトへの型を持っている必要があります。
-
[解決済み] 数値定数の前にunqualified-idを付けて、数値を定義することを期待する。
-
[解決済み】エラー。引数リストに一致するコンストラクタのインスタンスがない
-
[解決済み] GCCで「文字列定数から'char*'`への非推奨の変換」という警告を消すにはどうしたらいいですか?
-
[解決済み] gccでC/C++のソースからアセンブラ出力を得るにはどうしたらいいですか?
-
[解決済み】CMakeでGCCとClang/LLVMの切り替えを行う。
-
[解決済み】GCCを使用して読みやすいアセンブリを作成しますか?
-
[解決済み] 私のLinux開発プロジェクトにおけるClangとGCCの比較
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】構造体のベクター初期化について
-
[解決済み] error: 'ostream' does not name a type.
-
[解決済み】致命的なエラー LNK1169: ゲームプログラミングで1つ以上の多重定義されたシンボルが発見された
-
[解決済み】C++ 式はポインタからオブジェクトへの型を持っている必要があります。
-
[解決済み] クラスにデフォルトコンストラクタが存在しない。
-
[解決済み】C++の変数はイニシャライザーを持っているが、不完全な型?
-
[解決済み】デバッグアサーションに失敗しました。C++のベクトル添え字が範囲外
-
[解決済み】「Expected '(' for function-style cast or type construction」エラーの意味とは?
-
[解決済み】システムが指定されたファイルを見つけられませんでした。
-
[解決済み】警告 - 符号付き整数式と符号なし整数式の比較