でまあ、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は、悪くないと思うのだが。
> なぜ後でまとめてやらないのだろう。
ReplyDeleteペアリングの都合と予想
なるほど。そんなこともあるのですか。
ReplyDeleteしかしそれ、人間の手で最適化するのは困難だなぁ。