-- MMX Technology Reference --
MMX
テクノロジは、Intelが開発したx86 CPUの拡張機能の1つで、単一命令・複数データ(SIMD)技法を使用して、複数のデータ要素を並行して処理することにより、マルチメディアおよびコミュニケーション・ソフトウェアを高速化することが可能になります。MMX
テクノロジは、現在最新技術であるIntel-SSE(KNI)やAMD-3DNow!テクノロジの基礎になっています。従って、MMXテクノロジを理解することはSSEや3DNow!テクノロジを理解することにもつながり、今後登場するSIMDテクノロジに柔軟に対応することができるようになると思われます。本書はその
MMXテクノロジを理解することを目的とした解説書です。
MMX
テクノロジは現在流通しているプロセッサにはほぼ全てに搭載されていますが、Intel-Pentium以前のプロセッサには搭載されていないのでMMX命令が使用可能か調べる必要があります。MMX
テクノロジを検出するにはCPUID命令を使用します。CPUID命令はCPUから、そのCPU固有の情報を得る命令です。しかし、そのためにはアセンブラコードを必要とします。では、具体的な方法をC言語(インラインアセンブラ)を用いて解説します。
C言語(インラインアセンブラ)例:
//**********************************************************************
// MMXテクノロジ検出関数
// BOOL IsMMXEnabled(void)
// 戻り値:
// TRUE (0以外) … MMXテクノロジが検出された
// FALSE (0) … MMXテクノロジが検出されなかった//**********************************************************************#define MMX_FLAG 0x00800000 //MMX検出ビットの定義
#define CPUID __asm _emit 0x0F __asm _emit 0xA2 //CPUID命令を定義BOOL IsMMXEnabled(void)
{
int flag=0;_asm
{//=== インラインアセンブラ開始 ===========================push eax //レジスタ退避
push ebx //レジスタ退避
push ecx //レジスタ退避
push edx //レジスタ退避mov eax, 1 //レジスタeaxに 1を代入
CPUID //CPUID命令
mov flag, edx //検出用フラグ取得
pop edx //レジスタ復元
pop ecx //レジスタ復元
pop ebx //レジスタ復元
pop eax //レジスタ復元}//=== インラインアセンブラ終了 ===========================
if( (flag & MMX_FLAG) == 0 )
{
return FALSE;
}
return TRUE;
}
//**********************************************************************
解説:
まず、レジスタ
eaxに 1を代入し、CPUID命令( 0FA2 )を実行します。すると自動的にedxにそのプロセッサに搭載されているテクノロジのフラグがセットされます。それを取得し、判定することでMMXテクノロジの検出が行えます。これを少々改良すれば、現在の最新のテクノロジ、及び今後登場するテクノロジをも検出することが可能です。具体的には
if((flag & XXX)==0) のXXXを変えればよいだけです。Intelの SSE(KNI)ならば 0x02000000 、AMDの3DNow!ならば0x80000000です。これらの値は各メーカーの、それぞれのプロセッサのデータシートを調べれば見つかるはずです。この関数で
FALSEが帰ってきた場合はそのテクノロジが使用不可ということになります。3章.MMXレジスタ・セット |
MMXレジスタ / FPUレジスタ:
MMX
命令にはMMXレジスタが使用され、それはMM0 〜 MM7 という 8つの新しい64 ビットレジスタです。しかし、実はFPUレジスタと共有されており、MMXモード/FPUモードを切り替えて使用しています。MMX
モード/FPUモードの切り替えは、FPU → MMX への切り替えは自動ですが、 MMX → FPUへの切り替えは命令を要します。実際のプログラムでは、なるべくMMX命令とFPU命令を混在して使用しないようにし、MMXコードの終わりには「EMMS」という命令を実行してMMXレジスタをクリアするようにします。
モード切り替えペナルティ:
EMMS
命令を使用してMMX命令とFPU命令を混在して使用することも可能ですが、モード切り替えのペナルティ(最大50クロック)が大きいため、できるだけこの切り替えを少なくすることが重要です。4章.データタイプ |
MMX
命令は専用の新しいデータ型を使用します。それが「パックド整数値」とよび、下の図のように64ビットサイズで、1バイト整数×8個、2バイト整数×4個、4バイト整数×2個、8バイト整数×1個の4種類あります。また、この半分の32ビットサイズで、1バイト整数×4個、2バイト整数×2個、4バイト整数×1個を扱うこともできます。
パックド・バイト: 64 ビットにパックされた8 バイト
1 バイト |
1 バイト |
1 バイト |
1 バイト |
1 バイト |
1 バイト |
1 バイト |
1 バイト |
64 ビットにパックされた4 ワード
パックド・ワード:
2 バイト |
2 バイト |
2 バイト |
2 バイト |
64 ビットにパックされた2 ダブルワード
パックド・ダブルワード:
4 バイト |
4 バイト |
64 ビットクワッドワード
クワッドワード:
8 バイト |
実際にはこれらは変数として宣言したりすることはできません。char mem[8]; などとして普通にメモリに割り当てます。
5章.パック/アンパック |
→ パックド・バイト、パックド・ダブルワード → パックド・ワードのようにパックされたそれぞれのデータをさらに上位ビットをカットして1つのデータサイズを小さくすることです。パックとはパックド・ワード
アンパックとはパックの逆で、パックド・バイト
→ パックド・ワード、パックド・ワード → パックド・ダブルワードのようにパックされたそれぞれのデータを大きなサイズに変換することです。簡単に言うとデータのキャストを複数同時に処理すると考えられます。
パックの例:
A |
B |
C |
D |
↓
A |
B |
C |
D |
アンパックの例:
A |
B |
C |
D |
↓
A |
B |
C |
D |
6章.Wraparound / Satulation |
Wraparound:
通常、char などの変数には最大値があり、最大値を超えると最小値に戻ってしまいます。例えばchar型の変数に127(最大値)を代入し、さらに1を足すと
?128 になってしまいます。これをWraparoundといいます。これを防ぐ為には計算をうまくやるか、予想される最大値に収まる型を使用しなければなりません。また、最小値に対しても同様のことが起こります。
127 (0x7F) |
+1 |
= |
-128 (0x80) |
Satulation:
これに対し、Satulationは例えばchar型の変数に127(最大値)を代入し、さらに1を足してもその最大値を超えたり、Wraparoundせずに最大値を保持します。これは最小値に対しても同様の機能を果たします。SatulationはMMX演算時の特殊な機能でこれを知っておくと便利なことがあります。
127 (0x7F) |
+1 |
= |
127 (0x7F) |
実例:
実際には、例えばWindowsにおいて画像の1pixelの1成分(RGBのうちの1つ)は0~255なのでこの範囲内の値を保持しておけばよいのですが、計算時にこれを超えることがあります。そのときに超えているかどうかの判定をしなければならないところがしなくてもよくなります。具体的にプログラムでは、以下のようなことが起こります。
//***************************************************
int r,g,b; // int → unsigned char とすることができる
r = r + blightness;
if( r > 255 ){ r = 255; } // ← ここが不要になる
//***************************************************
MMX
テクノロジではこのSatulationの機能を使用することができ、コードの簡略化および実行速度の高速化をすることができます。7章.SIMD |
SIMDとは:
SIMD
はSingle Instruction Multiple Data の略で、1つの命令で複数のデータを扱い、並列に処理することで高速化を図る技術です。例えば、パックド・バイトならば1度に8つのデータを加算・減算などして処理できることになります。SIMDの例:
下のような8個の要素をもつ配列 A、B、C があったとします。
A = |
A1 |
A2 |
A3 |
A4 |
A5 |
A6 |
A7 |
A8 |
B = |
B1 |
B2 |
B3 |
B4 |
B5 |
B6 |
B7 |
B8 |
C = |
C1 |
C2 |
C3 |
C4 |
C5 |
C6 |
C7 |
C8 |
= A1 + B1 C2 = A2 + B2A、Bのそれぞれの要素を足して結果をCに代入したいとすると、
C1
C3
= A3 + B3 C4 = A4 + B4C5
= A5 + B5 C6 = A6 + B6C7
= A7 + B7 C8 = A8 + B8のように8回演算処理しなければなりません。
ところが、
SIMDでは、
C = A + B とするだけで同様の結果を得ることができます。
SIMDの長所:
・プロセッサでは
「メモリからレジスタへ移動」 → 「演算」 → 「メモリへ戻す」の作業の回数が少なくなるため、メモリアクセスタイミング等も考慮して非常に効率よくメモリアクセスができます。・1度に複数処理をするのでループの回数を減らすことができます。ループの回数が減れば、ループ終了判定回数が減ることになり、そのぶん高速化します。
SIMDの短所:
・データ数に制限が起こります。
N個同時に演算できるSIMDならば確保するデータのサイズはNの倍数である必要があり、処理できる個数もNの倍数となります。従って、無駄に多く処理してしまう場合や、少ない場合は残りのデータを処理するためにSIMDでない処理をすることになります。
8章.MMX Instructions |
命令表記フォーマット・法則:
MMX
命令表記はプリフィックス、命令、サフィックスからなっています。・プリフィックス
… P (パックドの略)・命令 …
ADD や MOVなど
・サフィックス … Wraparound / 符号あり・なしのSatulation と データ型(
パックド・バイト /パックド・ワード / パックド・ダブルワード/ クワッドワード ) を指定する。
特に表記しないWraparound :
符号ありSatulation :
S符号なしSatulation :
USパックド・バイト : B
パックド・ワード : W
パックド・ダブルワード : D
クワッドワード : Q
まとめると次のようになります。
[P][Instruction][S/US][B/W/D/Q]
表記例: |
|
説明 |
|
この章での表の見方:
命令 |
B |
W |
D |
Q |
説明 |
MOVD |
× |
× |
○ |
× |
32 ビットのデータをMMXレジスタに転送 |
命令 |
… 命令名 |
B/W/D/Q |
|
B |
… パックド・バイト |
○ |
使用可 |
W |
… パックド・ワード |
× |
使用不可 |
D |
… パックド・ダブルワード |
→ |
上位データ型へ変化 |
Q |
… クワッドワード |
← |
下位データ型へ変化 |
Data-Transfar Instructins (データ転送命令)
データをMMXレジスタに転送する命令。
命令 |
B |
W |
D |
Q |
説明 |
MOVD |
× |
× |
○ |
× |
32 ビットのデータをMMXレジスタに転送 |
MOVQ |
× |
× |
× |
○ |
64 ビットのデータをMMXレジスタに転送 |
例:
movd mm0 mem1 (32ビットデータをメモリからMMXレジスタへ)
movq mem2 mm1 (64ビットデータをMMXレジスタからメモリへ)
movq mm1 mm2 (64ビットデータをMMXレジスタからMMXレジスタへ)
Add / Sub Instructions (加算/減算命令)
命令 |
B |
W |
D |
Q |
説明 |
PADD |
○ |
○ |
○ |
× |
加算( Wraparound) |
PADDS |
○ |
○ |
× |
× |
符号あり加算( Satulation) |
PADDUS |
○ |
○ |
× |
× |
符号なし加算( Satulation) |
PSUB |
○ |
○ |
○ |
× |
減算( Wraparound) |
PSUBS |
○ |
○ |
× |
× |
符号あり減算( Satulation) |
PSUBUS |
○ |
○ |
× |
× |
符号なし減算( Satulation) |
例:
paddb mm1 mm2
(BYTE型加算(Wraparound))
paddsw mm2 mm3 (WORD型符号あり加算(Satulation))
psubusb mm4 mm5 (BYTE型符号なし減算(Satulation))
Shift Instructions (シフト命令)
命令 |
B |
W |
D |
Q |
説明 |
PSLL |
× |
○ |
○ |
○ |
左シフト演算(つねに0で埋める) |
PSRA |
× |
○ |
○ |
× |
右シフト演算(最上位ビットで埋める) |
PSRL |
× |
○ |
○ |
○ |
右シフト演算(つねに0で埋める) |
例/詳細:
psllw mm1 3
(WORD型左シフト演算)
mm1 |
1100011000001010 |
1110100001011000 |
0101110010110000 |
0001100001011011 |
それぞれのWORDを左へ3つシフトする
mm1 |
0011000001010000 |
0100001011000000 |
1110010110000000 |
1100001011011000 |
psrad mm6 5
(WORD型右シフト演算)
mm6 |
11000110000010101110100001011000 |
01011100101100000001100001011011 |
それぞれのDWORDを右へ5つシフトし最上位ビットは元の最上位ビットで埋める
mm6 |
11111110001100000101011101000010 |
00000010111001011000000011000010 |
Logical Instructions (論理演算命令)
命令 |
B |
W |
D |
Q |
説明 |
PAND |
○ |
○ |
○ |
○ |
ビットごとの AND演算 |
PANDN |
○ |
○ |
○ |
○ |
ビットごとの NOT・AND演算 |
POR |
○ |
○ |
○ |
○ |
ビットごとの OR演算 |
PXOR |
○ |
○ |
○ |
○ |
ビットごとの XOR演算 |
例:
pandn mm1 mm2
(mm1 のビットごとのNOT と mm2 のANDをとる mm1 = (~mm1)&mm2 )
※pandqのようにqはつけない。(B/W/D/Qの区別が無い)
Multiply Instructions (掛算命令)
命令 |
B |
W |
D |
Q |
説明 |
PMADD |
× |
→ |
× |
× |
WORD の掛け算をDWORDに出力 |
PMULH |
× |
○ |
× |
× |
掛け算の結果の上位 WORDを出力 |
PMULL |
× |
○ |
× |
× |
掛け算の結果の下位 WORDを出力 |
例/詳細:
pmaddwd mm1 mm2
mm1 |
A1 |
A2 |
A3 |
A4 |
mm2 |
B1 |
B2 |
B3 |
B4 |
↓演算後
mm1 |
C1 |
C2 |
pmaddwd
は次のような演算をする。C1
= A1 × B1 + A2 × B2
C2 = A3 × B3 + A4 × B4
Compare Instructions (比較命令)
命令 |
B |
W |
D |
Q |
説明 |
PCMPEQ |
○ |
○ |
○ |
× |
比較命令(=かどうか) |
PCMPGT |
○ |
○ |
○ |
× |
比較命令(>かどうか) |
例/詳細:
PCMPEQW mm1 mm3
mm1 |
0070 |
1000 |
0400 |
0008 |
mm3 |
0070 |
2000 |
5400 |
0008 |
↓演算後
mm1 |
FFFF |
0000 |
0000 |
FFFF |
WORD
のそれぞれを比較して等しいならばFFFFをセットし、それ以外は0にセットする。
Pack / Unpack Instructions (パック/アンパック命令)
命令 |
B |
W |
D |
Q |
説明 |
PACKSS |
× |
← |
← |
× |
符号あり Satulationパック |
PACKUS |
× |
← |
× |
× |
符号なし Satulationパック |
PUNPCKH |
→ |
→ |
→ |
× |
上位 32ビットアンパック |
PUNPCKL |
→ |
→ |
→ |
× |
下位 32ビットアンパック |
例/詳細:
mm2
及びmm3を以下の様に代入したとする。
mm2 |
1111 |
2222 |
3333 |
4444 |
mm3 |
aaaa |
bbbb |
cccc |
dddd |
PACKSSWB mm2 mm3
演算後:
mm2 |
80 |
80 |
80 |
80 |
7F |
7F |
7F |
7F |
それぞれの値が
127 (0x7f)よりも大きい場合は127に、-128(0x80)よりも小さい場合は-128にSatulationされる。(下位4バイトにmm2の結果、上位4バイトにmm3の結果が合成されて出力される)
PACKUSWB mm2 mm3
演算後:
mm2 |
00 |
00 |
00 |
00 |
FF |
FF |
FF |
FF |
それぞれの値が
255 (0xFF)よりも大きい場合は255に、0 (ここでWORDは符号ありで見られるため0x8000、つまり ?1 )よりも小さい場合は0にSatulationされる。
PUNPCKHWD mm2 mm3
演算後:
mm2 |
aaaa1111 |
bbbb2222 |
mm2
とmm3のそれぞれの上位32ビットのうちのさらに上位16ビット/下位16ビットが結合されて出力される。
PUNPCKLWD mm2 mm3
演算後:
mm2 |
cccc3333 |
dddd4444 |
mm2
とmm3のそれぞれの下位32ビットのうちのさらに上位16ビット/下位16ビットが結合されて出力される。
EMMS Instruction (EMMS命令)
命令 |
B |
W |
D |
Q |
説明 |
EMMS |
− |
− |
− |
− |
MMX モード終了 |
MMX
レジスタのMMX状態をクリアする。
9章.MMXテクノロジの使用/実験 |
MMXテクノロジの使用:
・
MMXテクノロジを使用するにはアセンブラ言語で記述する必要があります。VisualC++ならばインラインアセンブラで実現できます。ただし、VisualC++ Ver4.2以降でないと第8章で解説したようなMMX命令に対応していないので、”_emit” などで直接機械語を入力することになります。以下にインラインアセンブラでの記述例を示します。
void MMXalpha( void* pSrc1, void* pSrc2, void* pDst )
{
_asm
{
mov eax, 7f7f7f7fh
movd mm4, eaxpunpckldq mm4, mm4
mov eax, pDst
mov esi, pSrc1
mov edi, pSrc2movq mm0, [esi] //64ビット転送
movq mm1, [edi]psrlq mm0, 1 //右に1シフト( 2で割る )
psrlq mm1, 1pand mm0, mm4 //mm0 と mm4をAND演算
pand mm1, mm4paddusb mm0, mm1 //mm0 と mm1
を加算movq [eax], mm0 //
演算結果を出力先へ転送emms
}
}
・このサンプルは
pSrc1とpSrc2のポインタで示すデータを64ビット(1バイト×8個のデータ)読み出し、それぞれ半分に割り、足した結果をpDstで示す先へ出力するものです。
これを
C言語で書くと次のようになります。
unsigned int dst;
for( int n=0; n < 8; n++ )
{dst = (*(pSrc1 + n) + *(pSrc2 + n)) /2;
if( dst < 255 ){ *(pDst + n) = (unsigned char)dst; }
else{ *(pDst + n) = 255; }
}
(注:引き数 pSrc1, pSrc2, pDstはvoid* ではなく、unsigned char* とする必要がある)
MMX命令の使用の流れ:
@ movd または movq でmm0 からmm7 までのどれかのMMXレジスタにデータを転送する。
A @を繰り返し、必要なデータをそろえる。
B パック/アンパック命令で、データ型をそろえる。
B MMXレジスタに対し、MMX命令(paddusb、psrlwなど)を実行する。
C Bを繰り返し、計算したい演算を実行する。
D パック/アンパック命令で、データ型をそろえる。
E movd または movq で演算結果出力先に転送する。
F EMMS 命令でMMX
状態をクリアする。
MMX命令の使用に関して注意すべきこと:
(1)EMMS命令の使用
MMX命令の使用の流れにおいて、Fは必ず必要です。Intelの“インテル・アーキテクチャ・ソフトウェア・ディベロッパーズ・マニュアル”によると、「FPU タグ・ワードがEMMS 命令によってリセットされる前に浮動小数点命令がFPU レジスタ・スタックのレジスタの1 つに値をロードした場合は、浮動小数点スタック・オーバフローが発生する可能性があり、その結果、浮動小数点例外が発生したり、誤った結果が生じることになる。」 とありますので注意する必要があります。もし、アプリケーションレベルで浮動小数点を使用しない場合においてもEMMS命令は使用しなくてはなりません。
(2)アセンブラ言語の理解
MMX命令はアセンブラ言語で記述するため、アセンブラ言語をある程度理解しておくことが必要です。
(3)その他
MMX
命令はSIMDであることに特に注意し、MMX命令を使用するべき部分、そうでない部分を見極めることが大切です。また、データ型も特殊であるため、この点も特に注意しなければなりません。
MMXテクノロジの実験 − 画像処理:
・
MMX演算または通常演算で2つの画像をアルファブレンドするプログラムを作成し、実験をしてみた。
とBの画像をある一定の割合(alpha)で足す合成処理を考えます。 alphaは 0.0〜1.0 とし、このときの、Aの(X,Y)座標とBの(X,Y)座標の画素をA(X,Y)、B(X,Y)とすると合成後の画素 P(X,Y)は、
|
<実験結果>
MMX
演算(1)は4個同時、MMX演算(2)は8個同時のMMX演算を使用した。
画像サイズ |
通常演算( ms) |
MMX 演算(1)(ms) |
MMX 演算(2)(ms) |
高速化(1) |
高速化(2) |
2048 ×1536 |
約 612 |
約 212 |
約 200 |
287% |
306% |
1024 ×768 |
約 154 |
約 53 |
約 50.5 |
290% |
305% |
512 ×384 |
約 39 |
約 14.5 |
約 13 |
269% |
300% |
256 ×192 |
約 10 |
約 3 |
約 3 |
333% |
333% |
− 300MHz、192MB Mem、Matrox - Millenium2。テスト環境: Intel Pentium2
<結果考察>
MMX演算の方が約3倍高速化した。(1)も(2)も、理論的には計算部分を4個(8個)同時に演算しているので4倍(8倍)高速になるはずですが、1つの演算にかかる実行時間の違いにより、理論どおりにならなかったと考えられます。通常演算を1とするとMMX演算(1)では約1.3、MMX演算(2)では約2.7倍と考えることができます。・実験結果は表のとおり、通常演算よりも
その他のSIMDテクノロジの使用(VisualC++):
・
VisualC++ はMMXテクノロジ以外のSIMDテクノロジ(Intel - Internet Streaming SIMD Extensionや、AMD−3DNow!テクノロジ/エンハンスド3DNow!テクノロジ等)には対応していません。しかし、AMDの開発サイトから開発キットをダウンロードし、この開発キットを組み込めば3DNow!テクノロジ及びエンハンスド3DNow!テクノロジを使用することができます(もちろんインラインアセンブラレベルですが)。 Intel の SSEは現時点では対応の開発キットを見つけることができませんでした(MASM用のものしかありませんでした。)。
10章.参考文献/資料 |
・インテル・アーキテクチャ・ソフトウェア・ディベロッパーズ・マニュアル(上巻: 基本アーキテクチャ)
→インテルのサイトからダウンロードすることができます。
(アドレス: http://www.intel.co.jp/jp/developer/design/pentiumiii/manuals/index.htm )
・インテル・アーキテクチャ MMXR テクノロジプログラマーズ・リファレンス・マニュアル
(→以前にインテルのサイトからダウンロードした。)
-- MMX Technology Reference Ver 1.01 -- Copyright (C) 1999-2002 by Syn-K.