2010-12-26 1 views
17

注意を払う:その後なぜpushの代わりにmovlを使用していますか?このコードへ

#include <stdio.h> 
void a(int a, int b, int c) 
{ 
    char buffer1[5]; 
    char buffer2[10]; 
} 

int main() 
{ 
    a(1,2,3); 
} 

gcc -S a.c 

アセンブリにおける当社のソースコードを表示するコマンド。

今私たちはメイン関数で見ることができます、我々は決しての引数をスタックにプッシュするために "プッシュ"コマンドを使用しません。その代わりに「モベル」を使用しました

main: 
pushl %ebp 
movl %esp, %ebp 
andl $-16, %esp 
subl $16, %esp 
movl $3, 8(%esp) 
movl $2, 4(%esp) 
movl $1, (%esp) 
call a 
leave 

なぜ起こりますか? それらの違いは何ですか?

答えて

16

はgccのマニュアルがそれについて言いたいことです:

-mpush-args 
-mno-push-args 
    Use PUSH operations to store outgoing parameters. This method is shorter and usually 
    equally fast as method using SUB/MOV operations and is enabled by default. 
    In some cases disabling it may improve performance because of improved scheduling 
    and reduced dependencies. 

-maccumulate-outgoing-args 
    If enabled, the maximum amount of space required for outgoing arguments will be 
    computed in the function prologue. This is faster on most modern CPUs because of 
    reduced dependencies, improved scheduling and reduced stack usage when preferred 
    stack boundary is not equal to 2. The drawback is a notable increase in code size. 
    This switch implies -mno-push-args. 

どうやら-maccumulate-outgoing-args-mpush-argsをオーバーライドし、デフォルトで有効になっています。 -mno-accumulate-outgoing-argsで明示的にコンパイルすると、PUSHメソッドに戻ります。

+4

これは、この膨大な生成オプション '-maccumulate-outgoing-args'が' -Os'によって自動的に無効化されない理由です。 –

+0

@R ..だからあなたはなぜそれを知っていますか? – Tony

+0

@Tony:明らかに、特定の-Oオプションごとに多くの(〜200)個の最適化フラグのうちのどれを有効/無効にするかを決めるときに、状況がクラックする可能性があります。 – ninjalj

8

そのコードは、(更新された)スタックポインタ(esp)からのオフセット位置に定数(1,2,3)を直接置くだけです。コンパイラは、同じ結果を手動で "プッシュ"することを選択しています。

"push"はデータを設定し、スタックポインタを更新します。この場合、コンパイラはスタックポインタの更新を1つだけ減らします(3つ)。興味深い実験は、関数 "a"を1つの引数だけ取るように変更し、命令パターンが変化するかどうかを調べることです。

+0

なぜ定数を最初にレジスタに入れる必要がありますか? x86は即時定数のプッシュをサポートします – Necrolis

+0

@Necrolis:十分に公正です。編集されました。どうも。 –

0

Pentium命令セットには、定数をスタックにプッシュする命令はありません。だから、遅くなるpushを使用して:

... 
movl $1, %eax 
pushl %eax 
... 

だからコンパイラはmovlを使用して高速であることを検出します。プログラムは、レジスタに定数を入れて、レジスタをプッシュする必要があります。 私はあなたの代わりに、一定の変数を使用して関数を呼び出す試すことができますね。

int x; 
scanf("%d", &x); // make sure x is not a constant 
a(x, x, x); 
+6

定数を押すことは80286からサポートされています。おそらくgccはデフォルトで8086のコードを生成していますか? –

+1

x86命令セットに関する私の知識は少し古いです(20年後):-) – anatolyg

6

gccが用に最適化されている特定のCPUの実行速度に基づいて指示を選択するなど、最適化のすべての種類を、行います。 x *= nのようなものは、特にnが定数の場合、SHL、ADD、および/またはSUBの組み合わせに置き換えられることがよくあります。 MULは、SHL-ADD-SUBの組み合わせの平均ランタイム(およびキャッシュ/ etcフットプリント)がMULのそれを超えるか、またはnが定数ではない場合にのみ使用されます(したがって、shl-add-sub wouldよりコストがかかる)。

関数引数の場合:MOVはハードウェアで並列化できますが、PUSHでは並列化できません。 (2番目のPUSHは、espレジスタの更新のために最初のPUSHが終了するのを待たなければなりません)。関数引数の場合、MOVは並列に実行できます。

+0

この種の最適化に関する参考資料はありますか?ありがとう。 – Tony

2

OS Xではこれは万一ですか?スタックポインタを16バイトの境界に揃える必要があると私はどこかで読んでいます。これはおそらくこの種のコード生成を説明することができます。

私は記事を見つけた:ここhttp://blogs.embarcadero.com/eboling/2009/05/20/5607

+1

OS X ABIは、外部関数呼び出しの時点でスタックポインタが16バイトに整列することのみを要求します。 –

+0

私は、それを指摘してくれてありがとう、参照してください。他の答えを読む私は今movlコードの生成がスケジューリングの改善に関連していることを理解しています。 andl命令はスタックの整列のためだけにあるようです。 –

関連する問題