2016-12-31 1 views
7

昨日私はVC++ 2010で生成された32ビットコードを見ていましたが(おそらく、特定のオプションについてはわかりません)、興味深い繰り返しの細部に興味がありました。多くの機能で、ebxプロローグであり、常に「ゼロレジスタ」(MIPSの$zeroと思う)のように使用しました。特に、多くの場合:なぜVC++ 2010はebxを「ゼロレジスタ」として使用するのですか?

  • メモリをゼロにするために使用されます。 mov mem,immのエンコーディングがmov mem,regより大きい1から4バイトです(完全な即値のサイズは0でもエンコードする必要があります)が、通常は(gcc)必要なレジスタは「オンデマンド」からゼロになります。そうでなければより有用な目的のために保管された。
  • cmp reg,ebxのように0と比較して使用します。これはちょうどtest reg,regと同じでなければならないので、私は本当に珍しいものとしてストロークしますが、余分なレジスタに依存します。さて、これは非リーフ関数で起こったことを覚えておいてください。ebxは(呼び出し先で)スタックに入れたり出たりしばしばプッシュされるので、この依存関係を常に完全にフリーであるとは信じません。また、でも、は、全く同じ方法(test/cmp =>jg)でtest reg,regを使用しています。

最も重要なのは、古典的なx86上のレジスタは希少なリソースであるため、正当な理由がなくても多くの時間を無駄にしてしまうレジスタをこぼさなければならないことです。なぜそれをゼロに保つためにすべての機能を通して1つを無駄にするのでしょうか? (それでも、それについて考えてみると、この「ゼロレジスタ」パターンを使用した関数ではレジスタリークが多く見られることは覚えていません)。

So:私は何が欠けていますか?それはコンパイラブローカーか、または2010年に特に興味深かった非常にスマートな最適化ですか?

は、ここに抜粋です:

; standard prologue: ebp/esp, SEH, overflow protection, ... then: 
    xor  ebx, ebx 
    mov  [ebp+4], ebx  ; zero out some locals 
    mov  [ebp], ebx 
    call function_1 
    xor  ecx, ecx   ; ebx _not_ used to zero registers 
    cmp  eax, ebx   ; ... but used for compares?! why not test eax,eax? 
    setnz cl     ; what? it goes through cl to check if eax is not zero? 
    cmp  ecx, ebx   ; still, why not test ecx,ecx? 
    jnz  function_body 
    push 123456 
    call throw_something 
function_body: 
    mov  edx, [eax] 
    mov  ecx, eax   ; it's not like it was interested in ecx anyway... 
    mov  eax, [edx+0Ch] 
    call eax     ; virtual method call; ebx is preserved but possibly pushed/popped 
    lea  esi, [eax+10h] 
    mov  [ebp+0Ch], esi 
    mov  eax, [ebp+10h] 
    mov  ecx, [eax-0Ch] 
    xor  edi, edi   ; ugain, registers are zeroed as usual 
    mov  byte ptr [ebp+4], 1 
    mov  [ebp+8], ecx 
    cmp  ecx, ebx   ; why not test ecx,ecx? 
    jg  somewhere 

label1: 
    lea  eax, [esi-10h] 
    mov  byte ptr [ebp+4], bl ; ok, uses bl to write a zero to memory 
    lea  ecx, [eax+0Ch] 
    or  edx, 0FFFFFFFFh 
    lock xadd [ecx], edx 
    dec  edx 
    test edx, edx   ; now it's using the regular test reg,reg! 
    jg  somewhere_else 

注意:この質問の以前のバージョンは、それがmov reg,ebx代わりのxor ebx,ebxを使用したことを言いました。これはちょうど正しく物事を覚えていない私でした。誰もがそれを理解しようと思ってあまりにも多くの考えを入れれば申し訳ありません。

+1

私はあまりにも疲れていました。私はもう一度チェックし、関数全体(これは珍しいものとして私をストロークさせるもの)のために 'ebx'をゼロに保つのは本当ですが、' mov 'のエンコーディングは'cmp reg、ebx'(' ebx' == 0)はプレーンと同じでなければならないので'test reg、reg'です。私はそれに応じて質問を更新します。 –

+0

xDは、私の答えを入力中にポップアップしたコメントをクリックしてクリックする必要があります:Pそしてはい、 '' test reg、reg'は '' cmp''と同じフラグをゼロに設定します(http://stackoverflow.com/ a/38032818/224132)(即時またはレジスタ)、ただしAFは未定義のままです。 Perf-Wiseの場合、より多くのCPUでより多くの場合マクロテストが可能になり、デッドレジスタの読み込みを避けることができます(P6の助けになりますので、1993年に適用されます)。 –

+1

コンパイラは速度の代わりにサイズを最適化している可能性があります。 –

答えて

5

私が奇妙に見えるものはすべて私には最適ではありません。 test eax,eax sets all flags (except AF) the same as cmp against zeroであり、パフォーマンスとコードサイズに適しています。

P6(PProからNehalem)では、レジスタ読み込みストールが発生する可能性があるため、ロングデッドレジスタの読み込みが悪いです。 P6コアは、クロックごとに永続的なレジスタファイルから2つまたは3つの最近変更されたアーキテクチャレジスタのみを読み取ることができます(発行ステージのオペランドをフェッチするために:ROBはSnBファミリのオペランドを保持します。物理レジスタファイル)。

これはVS2010のものなので、Sandybridgeはまだリリースされていないので、 "cold"レジスタを読み込むPentium II/III、Pentium-M、Core2、Nehalemのチューニングには重い負担が必要です可能なボトルネック。

IDKこれが整数のregsの意味でこれに当てはまるとすればIDKですが、P6より古いCPUの最適化についてはあまりよく分かりません。


CMP/setz/CMP/JNZシーケンスは特に脳死に見えます。たぶん、それは何かからブール値を生成するためのコンパイラ内部の缶詰シーケンスから来て、ブール値のテストをフラグを直接使うだけに最適化できませんでしたか?それでも、ゼロレジスタとしてのebxの使用については説明されていませんが、そこではまったく役に立たないものです。

これは、inline-asmがブール値の整数を返す可能性がありますか(レジスタ内でゼロを求めている愚かなものを使用しています)。

または、ソースコードが2つの未知の値を比較していた可能性があります。インライン化と定数伝​​播の後で、ゼロとの比較に変わったのでしょうか?どのMSVCが完全に最適化できなかったので、testではなく、レジスタに定数として0を保持していましたか?


(残りは質問に含まれるコードの前に書かれています)。

奇妙に聞こえる、またはCSE /常時巻上げランのようです。つまり、一度ロードしてreg-regコピーしたいかもしれない他の定数のように0を処理してください。

データ依存関係の分析は正しいです。少し前にゼロ化されたレジスタから移動すると、基本的に新しい依存関係チェーンが開始されます。


gccは2は、レジスタをゼロにそれが多いxor-aがゼロ1と、他にコピーするmovまたはmovdqaを使用したいと考えています。

これはSandybridge where xor-zeroing doesn't need an execution portで最適ではありませんが、movがAGUまたはALUで実行できるBulldozerファミリの可能性はありますが、xor-zeroingにはALUポートが必要です。

ベクトル移動の場合、実行ユニットなしでレジスタの名前を変更して処理することはブルドーザーにとっては明らかです。しかし、XMMまたはYMMレジスタのxor-zeroingには、Bulldozerファミリ(or two for ymm, so always use xmm with implicit zero-extension)に実行ポートが必要です。

しかし、私は、特に余分なセーブ/リストアを必要としない場合には、関数全体の持続時間の間にレジスタを縛ることを正当化するとは考えていません。また、P6ファミリCPUの場合、レジスタ読取りストールが問題になりません。

+0

"その中には、ブール整数を返したinline-asmからのものがある可能性はありますか?インラインアセンブリは除外しますが、元のC++ソースがないため、わかりませんが2つ目の選択肢はおそらくもっと見えますが、生成されたアセンブリは、まともな現代のコンパイラから可能であると予想していた以上に本当に悪いものです。 –

+0

@MatteoItalia:前回チェックしたとき、MSVCは、ヘルパー関数をインライン展開した後、ループからベクトル定数の負荷を吊り上げませんでした。まともに現代的かもしれないが、これは唯一の重大な欠落最適化問題ではない。場合によっては他のコンパイラも顔をそろえてしまいますが、MSVCは私が小規模で見たものよりもコードを悪化させます。そして、文法はレジスタ(/ facepalm)の入力を要求することができないため、MSVCスタイルのインラインasmはインラインasmへの入力を格納/リロードする必要があるため、インラインasmのようには見えませんでした。 –

関連する問題