2013-05-21 14 views
16

いくつかのQtコードをステップ実行しているうちに、私は次のことを見つけました。コンパイラがこのアセンブリを生成するのはなぜですか?

void QMainWindowLayout::invalidate() 
{ 
QLayout::invalidate() 
minSize = szHint = QSize(); 
} 

それはこれにコンパイルされています:無効+ 9から

<invalidate()>  push %rbx 
<invalidate()+1>  mov %rdi,%rbx 
<invalidate()+4>  callq 0x7ffff4fd9090 <QLayout::invalidate()> 
<invalidate()+9>  movl $0xffffffff,0x564(%rbx) 
<invalidate()+19>  movl $0xffffffff,0x568(%rbx) 
<invalidate()+29>  mov 0x564(%rbx),%rax 
<invalidate()+36>  mov %rax,0x56c(%rbx) 
<invalidate()+43>  pop %rbx 
<invalidate()+44>  retq 

アセンブリ+ 36は愚かなようで無効化する機能QMainWindowLayout::invalidate()は、次の実装があります。最初にコードは%rbx + 0x564と%rbx + 0x568に-1を書き込みますが、それを%rbx + 0x564から-1を%rbx + 0x56cに書き出すためのレジスタに戻します。これは、コンパイラがすぐに別の動きに簡単に最適化できるようなもののようです。

これは愚かなコードです(もしそうなら、コンパイラはそれを最適化しないのですか?)これはどういうわけか、非常に巧妙で高速です。

(注:このコードは、Ubuntuので出荷され、通常のリリースライブラリビルドからなので、それはおそらく最適化モードでGCCでコンパイルされたminSizeszHint変数はタイプQSizeの通常の変数です)

+3

QTは正しいUIですか?連続してウィンドウを無効にする必要が何回ですか?どのようにパフォーマンスが実際に必要なのだろうか?あなたが記述しているマイクロ最適化の種類は、発生する可能性のある最小限の利益のための努力に値するものではありません。 –

+0

それは実際には最適ではないようですが、覗きょうのオプティマイザがこれを取得していない可能性があります。 –

+11

@RobertHarveyしかし、それはここのポイントではない - OPは最適化しようとしていない、彼は理由を理解しようとしている。 –

答えて

13

わかりませんあなたはそれがばかだと言っている時あなたは正しい。私はコンパイラがここでコードサイズを最適化しようとしていると思います。 64ビット即値メモリ命令はありません。したがって、コンパイラは上記のように2つのmov命令を生成する必要があります。それらの各々は10バイトであり、生成される2つの移動は14バイトである。書かれているので、メモリの待ち時間がほとんどないので、ここでパフォーマンスが低下するとは思われません。

+0

...また、 'mov ...、(addr)'の後ろに 'mov(addr)、...'をつけた場合、第2はキャッシュホットです。つまり、ペナルティはほとんどありません。私がここで考えることができる唯一の最適化は 'pcmpeq%xmm0、%xmm0でした。 movdqu%xmm0、0x564(%rbx) 'を使って16バイト全体をすべて' 0xff..'に設定していますが、2つの変数をこの方法で「マージ」するのはかなり難しいです。 C++にロード/ストア可視性保証を提供します。 –

+2

+1 for * "メモリへの即時実行命令はありません"と言われただけです。 –

+0

私は64ビットの即時移動に関する部分を知らなかったので、それはおそらく解決策です。さらに、アライメントされていないメモリアクセスがキャッシュラインの境界を越えない場合、x86には実質的なコストはないようです。 – JanKanis

1

私は

これらの2行は個別に各フィールドを設定しQSize()http://qt.gitorious.org/qt/qt/blobs/4.7/src/corelib/tools/qsize.h のインライン定義から来ている(いくつかのコメントと同じ手順を持っていると思う)、このように複数行を打破したいです。また、私の推測では、0x564(%rbx)は、同時に設定されているszHintのアドレスです。

<invalidate()+9>  movl $0xffffffff,0x564(%rbx) 
<invalidate()+19>  movl $0xffffffff,0x568(%rbx) 

これらの行は、最終的には、コンパイラが今QSizeオブジェクトのサイズを知っているので、64ビット演算を使用してminSize設定されています。そしてminSizeのアドレスが0x56c(%のRBX)

<invalidate()+29>  mov 0x564(%rbx),%rax 
<invalidate()+36>  mov %rax,0x56c(%rbx) 

注意です。最初の部分は2つの別々のフィールドを設定し、次の部分はQSizeオブジェクトを(コンテンツに関係なく)コピーしています。それでは、コンパイラはコンパイルされた64ビット値を構築するのに十分なほどスマートでなければなりません。のプリセット値を直前に見たのですか?それについてはわかりません...

+2

はい、コンパイラは一般的に、この種の最適化を行うことができます。それは一定の折り畳みとして知られています。 – JanKanis

+0

@Somejanクール、それを知らなかった:) – epatel

0

Guillaumeの回答に加えて、64ビットのロード/ストアは整列していません。しかし、Intel optimization guide(p 3-62)

誤ったデータアクセスは、大幅なパフォーマンスの低下を招く可能性があります。 これは、キャッシュライン分割で特に当てはまります。キャッシュ の行のサイズは、Pentium 4およびその他の最近のIntelプロセッサーでは64バイト、Intel Coreマイクロアーキテクチャーに基づくプロセッサーを含む です。

64バイト境界でアライメントされていないデータにアクセスすると、2つのメモリへのアクセスが発生し、(1つではなく)複数のμopsを実行する必要があります。64バイト境界にまたがる アクセスが大きな パフォーマンスの低下を招く可能性がある、各ストールのコストは、一般的に長いパイプラインで マシン上で大きくなっています。

これは、キャッシュラインの境界を越えないアライメントされていないロード/ストアが安価であることを意味しています。この場合、プロセスIのデバッグ中のベースポインタは0x10f9bb0であったため、2つの変数はキャッシュラインに20バイトと28バイトです。

通常、Intelプロセッサは、転送をロードするために店を使用し、これだけ保存された値の負荷でもキャッシュに触れる必要はありません。しかし、同じガイドでは、いくつかの小規模店舗の大規模な負荷がストア・ロードフォワードではなくストールすることも述べています。(p 3-66、p 3-68)

アセンブリ/コンパイラコーディング規則49影響、M一般性)ストアから転送される のデータは、完全にストアデータ内に に含まれていなければなりません。

; A. Large load stall 
mov  mem, eax  ; Store dword to address “MEM" 
mov  mem + 4, ebx ; Store dword to address “MEM + 4" 
fld  mem    ; Load qword at address “MEM", stalls 

だから、問題のコードは、おそらく失速の原因となるので、私はそれが最適ではないと考えているに傾いています。 GCCがこのような制限を十分に考慮していないと、私はあまり驚かないでしょう。 GCCのstore-to-load転送制限のモデリングがどれくらいの頻度であるかを知っていますか?

EDITは:に、minSize/szHintフィールドの前にフィラー値を追加することを試して、いくつかは、GCCは全くキャッシュラインの境界がどこにあるかを気にしないことを示しており、どちらも打ち鳴らすしません。

8

コードは「完璧な未満」です。コードサイズの

、これらの4つの命令は、34のバイトまで追加。はるかに小さいシーケンス(19バイト)が可能です:

00000000 31C0    xor eax,eax 
00000002 48F7D0   not rax 
00000005 48898364050000 mov [rbx+0x564],rax 
0000000C 4889836C050000 mov [rbx+0x56c],rax 

;Note: XOR above clears RAX due to zero extension 

パフォーマンスはあまり簡単ではありません。 CPUは同時に多くの命令を実行する必要があり、上のコードはそれを破ります。たとえば:

あなたはこれをしたいのパフォーマンスを得るために
xor eax,eax 
not rax     ;Must wait until previous instruction finishes 
mov [rbx+0x564],rax  ;Must wait until previous instruction finishes 
mov [rbx+0x56c],rax  ;Must wait until "not" finishes 

00000000 48C7C0FFFFFFFF  mov rax,0xffffffff 
00000007 C78364050000FFFFFFFF mov dword [rbx+0x564],0xffffffff 
00000011 C78368050000FFFFFFFF mov dword [rbx+0x568],0xffffffff 
0000001B C7836C050000FFFFFFFF mov dword [rbx+0x56c],0xffffffff 
00000025 C78370050000FFFFFFFF mov dword [rbx+0x570],0xffffffff 

;Note: first MOV sets RAX to 0xFFFFFFFFFFFFFFFF due to sign extension 

これは、命令のすべてがどこにも依存関係を持つ、並列に実行することができます。悲しいことに、それはもっと大きくなっています(45バイト)。

あなたは、コードサイズとパフォーマンスのバランスを取得しようとすると、 RAXの最後の命令がRAXの値を知る必要がある前に、最初の命令(RAXの値を設定する)が完了することを期待できます。これは次のようなものです。

mov rax,-1 
mov dword [rbx+0x564],0xffffffff 
mov dword [rbx+0x568],0xffffffff 
mov dword [rbx+0x56c],rax 

これは34バイト(元のコードと同じサイズ)です。これは、コードサイズとパフォーマンスとの間で優れた妥協点になりそうです。

Now;

mov dword [rbx+0x564],0xffffffff 
mov dword [rbx+0x568],0xffffffff 
mov rax,[rbx+0x564]    ;Massive problem 
mov [rbx+0x56C],rax    ;Depends on previous instruction 

現代のCPUは書き込みは、バッファに格納されている「店舗フォワーディング」と呼ばれるものを、持っていないと未来が避けるために、このバッファから値を取得することができます読み:悪いですなぜのは、元のコードを見て見てみましょうキャッシュから値を読み取ります。皮肉なことに、これは、読み込みのサイズが書き込みのサイズより小さいか等しい場合にのみ機能します。 2つの書き込みがあり、読み取りが両方の書き込みよりも大きいので、「ストア転送」はこのコードでは機能しません。これは、第3の命令が最初の2つの命令がキャッシュに書き込まれるまで待たなければならず、その後キャッシュからその値を読み取らなければならないことを意味する。それは約30サイクル以上のペナルティを容易に増加させる可能性がある。次に、4番目の命令は3番目の命令を待たなければならない(何かと並行して実行することはできない)ので、別の問題です。

+0

+1のintel構文を使用しています。簡単な質問ですが、元のコードは 'mov [rbx + 0x56C]、rax'ですが、最適化された例では' mov dword [rbx + 0x56C]、rax'です。これはあなたが4バイト(DWORD)を動かしている間、元の8バイト(QWORD)を '[rbx + 0x56c]'に移動することを意味しますか?これは意図されていますか? – greatwolf

関連する問題