2008-05-22

Compiler Intrinsicの吐くコード

でまあ、x264の開発者達は、Compiler Intrinsicを無視しているわけだが、実際にどんなコードを吐くのだろうか。

コード自体は、前回を見ていただくとして、インラインアセンブリにする前のコードは、魔法のアルゴリズムのところで言及している。

コードの処理内容は、三つの整数地のmedianを取りたい、というものだ。これが単なる平均というのであれば、単に全部足し合わせて、三にて除すればよいのだが、中央値となると、そうはいかない。最初のCのコードでは、最小値と最大値を求めて、合計から減ずるという方法を取っている。最小値と最大値を求めるには、if文が必要だ。x264とは動画のエンコーダであり、この処理が恐ろしく頻繁に必要になる。だから、このコードのクロック数をわずかでも少なくすることは、エンコード速度の向上につながる。たとえ、一回あたりが数十クロックから数百クロックの削減であったとしても、効果は絶大だ。

さて、現在のCコードは、シフト演算子をつかって、符号ビットを見ている。そうすることによって、最小値と最大値を、条件分岐を用いず、計算だけで出すことができる。もちろんこれは、規格では未定義のコードだ。しかし、どうせ実装依存というのであれば、プロセッサのSIMDな命令を使えば、もっと速くできるはずだ。とても短いコードで、インライン展開しなければ利点がないので、別途yasmなどでアセンブルして、リンカで、というわけには行かない。そんなわけで、インラインアセンブリが必要になる。

これを単純に、固定値で呼び出すと、次のコードを吐く。

mov     eax,1
movd    xmm0,eax
movq    xmm2,xmm0
mov     ecx,2
movd    xmm1,ecx
pmaxsw   xmm0,xmm1
mov     eax,3
movd    xmm3,eax
pminsw   xmm0,xmm3
pminsw   xmm1,xmm2
push    edx 
pmaxsw   xmm0,xmm1

悪くない。前回のコードと比べて、まったく遜色ない。gccのインラインアセンブリのところは、Intel Syntaxではなく、AT&T Syntaxなので、srcとdstが逆になっていることに注意されたい。では、実際にx264で使ってみた場合は、どのようなコードを吐くか見てみよう。以下のコードは、macroblock.c の183行である。

0041B017  . 66:0F6EC1   MOVD MM0,ECX
0041B01B  . 0FBF08     MOVSX ECX,WORD PTR DS:[EAX]
0041B01E  . 8B4424 10   MOV EAX,DWORD PTR SS:[ESP+10]
0041B022  . F3:      PREFIX REP:             ; Superfluous prefix
0041B023  . 0F7ED0     MOVD EAX,MM2
0041B026  . 66:0F6ECA   MOVD MM1,EDX
0041B02A  . 66:0FEEC1   PMAXSW MM0,MM1
0041B02E  . 5F       POP EDI
0041B02F  . 66:0F6ED9   MOVD MM3,ECX
0041B033  . 66:0FEACA   PMINSW MM1,MM2
0041B037  . 66:0FEAC3   PMINSW MM0,MM3
0041B03B  . 66:0FEEC1   PMAXSW MM0,MM1
0041B03F  . 5E       POP ESI
0041B040  . 66:0F7EC2   MOVD EDX,MM0

OllyDbgで見てみたが、よく分からないのは、間にpopが挟まっていることだ。この後すぐに、関数から、return するので、popをしなければならないというのは分かるのだが、なぜ後でまとめてやらないのだろう。何か理由があるのだろうか。

まあともかく、Compiler Intrinsicは、悪くないと思うのだが。

2 comments:

齊藤 said...

> なぜ後でまとめてやらないのだろう。

ペアリングの都合と予想

江添亮 said...

なるほど。そんなこともあるのですか。
しかしそれ、人間の手で最適化するのは困難だなぁ。