第8章では、いよいよMMX命令の使って画像処理を高速化する方法について説明したいと思います。 7.6節で述べたように、7.4節で説明したフェードアウトのプログラムと、7.5節で説明したフェードインのプログラムを高速化してみました。 いきなりプログラムの説明をすると大変なので、まずはMMXテクノロジとはどんな技術なのかという事から説明していきます。 既にMMXテクノロジについて理解している方は、8.2〜8.5節は読み飛ばして構いません。
MMXテクノロジは、画像や音声などのマルチメディア関係の処理を高速化するために生まれた技術です。 加算や減算などの演算を一つずつ行うのではなく、一度に複数の値同士を加算したり減算したりする事で、高速に処理をする事ができます。 この考え方は、Pentium3から搭載されたSSE命令(ストリーミングSIMD拡張命令)でも同じです。
MMX命令を利用する場合は、今まで利用してきたeaxレジスタやebxレジスタなどの他に、MMXレジスタと呼ばれるレジスタを利用します。 MMXレジスタにはmm0, mm1, mm2, mm3, mm4, mm5, mm6, mm7の8つがあり、どのMMXレジスタも64ビットのレジスタです。 MMX命令で扱うデータの型には、「パックド・バイト」、「パックド・ワード」、「パックド・ダブルワード」、「クワッドワード」の4種類があります。
「パックド・バイト」は、8ビット(1バイト)で表された整数を8つで1組にしたものです。 パックド・バイト同士の加算や減算を行う命令を利用すれば、8ビット(1バイト)で表された整数同士の加算や減算を一度に8つ行う事ができます。
「パックド・ワード」は、16ビット(2バイト)で表された整数を4つで1組にしたものです。 パックド・ワード同士の加算や減算を行う命令を利用すれば、16ビット(2バイト)で表された整数同士の加算や減算を一度に4つ行う事ができます。
「パックド・ダブルワード」は、32ビット(4バイト)で表された整数を2つで1組にしたものです。 パックド・ダブルワード同士の加算や減算を行う命令を利用すれば、32ビット(4バイト)で表された整数同士の加算や減算を一度に2つ行う事ができます。
「クワッドワード」は64ビット(8バイト)で表された整数なのですが、残念ながらクワッドワード同士の加算や減算を行う命令はありません。 ビットシフトなどの論理演算命令はあります。
通常の加算命令や減算命令では、演算の結果がオーバーフローしたりアンダーフローしたりすると、溢れた桁は無視されてしまいます。 その結果、正しい値を得る事ができません。 しかし、7.5節で説明したフェードインのプログラムのように、加算結果がオーバーフローしてしまう場合は最大値に丸め込みたい場合があります。 そこでMMX命令には、加算した結果がオーバーフローした場合最大値に丸め込んだり、アンダーフローした場合最小値に丸め込んでくれる命令が用意されています。 それが飽和加算命令です。
符号付きパックド・バイトの飽和加算を行うpaddsb命令では、加算結果が127を上回る場合は127に、-128を下回る場合は-128に丸め込んでくれます。 符号付きパックド・ワードの飽和加算を行うpaddsw命令では、加算結果が32767を上回る場合は32767に、-32768を下回る場合は-32768に丸め込んでくれます。 符号なしパックド・バイトの飽和加算を行うpaddusb命令では、加算結果が255を上回る場合は255に丸め込んでくれます。 符号なしパックド・ワードの飽和加算を行うpaddusw命令では、加算結果が65535を上回る場合は65535に丸め込んでくれます。
7.4節で説明したフェードアウトのプログラムのように、減算結果がアンダーフローしてしまう場合は最小値に丸め込みたい場合があります。 そこでMMX命令には、減算した結果がオーバーフローした場合最大値に丸め込んだり、アンダーフローした場合最小値に丸め込んでくれる命令が用意されています。 それが飽和減算命令です。
符号付きパックド・バイトの飽和減算を行うpsubsb命令では、減算結果が127を上回る場合は127に、-128を下回る場合は-128に丸め込んでくれます。 符号付きパックド・ワードの飽和減算を行うpsubsw命令では、減算結果が32767を上回る場合は32767に、-32768を下回る場合は-32768に丸め込んでくれます。 符号なしパックド・バイトの飽和減算を行うpsubusb命令、符号なしパックド・ワードの飽和減算を行うpsubusw命令では、減算結果が0を下回る場合は0に丸め込んでくれます。
残念ながら、飽和加算や飽和減算ができるのはパックド・バイト同士かパックド・ワード同士のみです。 パックド・ダブルワードやクワッドワードではできません。
MMX命令はMMX Pentium以降のCPUには搭載されていますが、Pentium以前のCPUには搭載されていません。 そのため、プログラムを動作させるマシンのCPUにMMX命令が搭載されているかどうか、MMX命令を利用する前にチェックする必要があります。
Pentium以降のCPUにはcpuidという命令があり、cpuid命令を利用する事でMMX命令の有無や、Pentium3以降のストリーミングSIMD命令の有無などをチェックする事ができます。 しかし、初期の頃の80486にはcpuid命令がないため、cpuid命令を利用する前にcpuid命令の存在もチェックしなければなりません。 cpuid命令の有無は、EFLAGSレジスタの21ビット目が書き換え可能であるかをチェックする事で確認できます。 それには、pushfd命令、popfd命令を利用します。
pushf命令はFLAGSレジスタ(EFLAGSレジスタの下位16ビット)をスタックに退避する命令ですが、pushfd命令はEFLAGSレジスタの32ビット全てをスタックへ退避する命令です。 popf命令はスタックへ退避しておいた値をFLAGSレジスタ(EFLAGSレジスタの下位16ビット)へ取り出す命令ですが、popfd命令はスタックへ退避しておいた値をEFLAGSレジスタへ取り出す命令です。
以上のことから、cpuid命令の有無は以下の手順でチェックする事ができます。
cpuid命令の存在をチェックできたら、次はMMX命令の有無をチェックします。 eaxレジスタに1を代入してcpuid命令を実行すると、edxレジスタに機能情報が入ります。 この機能情報の特定のビットを調べる事で、様々な機能の有無を調べる事ができます。 この機能情報の23ビット目が1であればMMX命令が存在することを示しており、0であればMMX命令が存在しない事を示しています。
|
|
8.4節の説明から、符号なしパックド・バイトの飽和加算を行うpaddusb命令を利用すると、7.5節で説明したフェードインのプログラムが簡単に高速化できそうな事が分かります。 そこで、paddusb命令を利用してフェードインを行うように7.5節のプログラムを修正してみましょう。
movd命令は、第一オペランドの下位32ビットに第二オペランドの値の下位32ビットを代入する命令です。 第一オペランドがmmxレジスタの場合、上位32ビットには0が代入されます。 第一オペランド及び第二オペランドには、MMXレジスタ(64ビット)、通常の32ビットレジスタ、メモリ(32ビット)の何れかが指定できますが、第一オペランドと第二オペランドの両方をメモリにする事はできません。
punpcklbw命令は、第一オペランドの下位4バイトと第二オペランドの下位4バイトを、第二オペランドへ交互に代入する命令です。 下のプログラムでは、punpcklbw命令を3回実行する事でp2の値をmm0レジスタの8つのバイトへ格納しています。
movq命令は、第一オペランドに第二オペランドの値(64ビット)を代入する命令です。 第一オペランド及び第二オペランドには、MMXレジスタ、メモリの何れかが指定できますが、第一オペランドと第二オペランドの両方をメモリにする事はできません。
paddusb命令では、第二オペランドにはMMXレジスタ又はメモリを指定する事ができます。 しかし、第一オペランドにはMMXレジスタしか指定する事ができません。 そこで、movq命令を利用してVRAM上のデータを一旦mm1レジスタに代入し、paddusb命令で飽和加算を行ってからmovq命令でVRAMを書き換えています。
MMXレジスタは、FPU命令を利用する際に利用されるFPUレジスタと共有されています。 このため、MMX命令を使い終わった後はFPUレジスタを利用できる状態に戻さなければなりません。 そのための命令がemms命令です。
7.3節の説明から分かるように、1行あたりのバイト数は4の倍数です。 従って、1行あたりのバイト数に行数をかけた全体のバイト数も4の倍数です。 しかし、全体のバイト数が8の倍数であるという保証はありません。 全体のバイト数が8で割り切れる場合と、4バイトってしまう場合があります。 全体のバイト数が8で割り切れず4バイト余ってしまう場合、余った4バイトはmovq命令の代わりにmovd命令を使って処理します。 movd mm1,[edi]で、ediレジスタが指しているメモリから32ビット分がmm1の下位32ビットに代入され、mm1の上位32ビットには0が代入されます。 movd [edi],mm1で、ediレジスタが指しているメモリへmm1の下位32ビットが代入されます。
|
|
フェードアウトの場合も高速化の手順はフェードインの場合と殆ど同じですので、mmxfadeoutの説明は省略します。 fadeout、mmxfadein、mmxfadeoutの3つを見比べてみてください。
|
|
MMX命令の使い方の説明はいかがでしたでしょうか? 実を言うと、第7章のプログラムを書き始めるまでは僕自身もMMX命令を使ったことがありませんでした。 MMX命令についていろいろ調べながら第7章と第8章を書いたため、誤っている点や改良すべき点があるかもしれません。 何かご意見がありましたら掲示板かE-mailでお知らせください。