なぜgccは残りの96個の整数だけではなく、配列全体をゼロで埋めるのですか?ゼロ以外の初期化子はすべて配列の先頭にあります。
void *sink;
void bar() {
int a[100]{1,2,3,4};
sink = a; // a escapes the function
asm("":::"memory"); // and compiler memory barrier
// forces the compiler to materialize a[] in memory instead of optimizing away
}
MinGW8.1とgcc9.2はどちらもこのようにasmを作成します(Godboltコンパイラエクスプローラー)。
# gcc9.2 -O3 -m32 -mno-sse
bar():
push edi # save call-preserved EDI which rep stos uses
xor eax, eax # eax=0
mov ecx, 100 # repeat-count = 100
sub esp, 400 # reserve 400 bytes on the stack
mov edi, esp # dst for rep stos
mov DWORD PTR sink, esp # sink = a
rep stosd # memset(a, 0, 400)
mov DWORD PTR [esp], 1 # then store the non-zero initializers
mov DWORD PTR [esp+4], 2 # over the zeroed part of the array
mov DWORD PTR [esp+8], 3
mov DWORD PTR [esp+12], 4
# memory barrier empty asm statement is here.
add esp, 400 # cleanup the stack
pop edi # and restore caller's EDI
ret
(SSEを有効にすると、movdqa load / storeで4つすべてのイニシャライザがコピーされます)
Clangのように、GCCが最後の96要素のみをlea edi, [esp+16]
(および)memsetを実行せず、なぜしないのrep stosd
ですか? これは逃した最適化ですか、それともこの方法で行う方が効率的ですか?(Clang memset
はインライン化する代わりに実際に呼び出しますrep stos
)
編集者注:質問には、元々同じように機能する最適化されていないコンパイラー出力がありましたが、非効率的なコードで-O0
は何も証明されません。しかし、この最適化はGCCでも見落とされてい-O3
ます。
a
非インライン関数へのポインターを渡すことは、コンパイラーを強制的に実体化させるもう1つの方法ですa[]
が、32ビットコードではasmが大幅に乱雑になります。(スタックの引数は、配列への初期化のためにスタックへのストアと混合されるプッシュをもたらします。)
を使用しvolatile a[100]{1,2,3,4}
てGCC を取得し、配列を作成してコピーします。通常volatile
、コンパイラーがローカル変数を初期化する方法、またはそれらをスタックに配置する方法を調べるのに適しています。
.rodata
ます... 400バイトのコピーが8つのアイテムのゼロ化と設定よりも速いとは思えません。
-O3
(実際に)発生することを確認するまで、驚くことではありません。 godbolt.org/z/rh_TNF
missed-optimization
キーワードを付けてGCCのバグジラに報告してください。
a[0] = 0;
と、その後a[0] = 1;
。