float型浮動小数を4つ並列に演算する機構です. MMXや3D Now!の様にFPUレジスタを利用しているのではなく全く新規に作られたレジスタです. そのため, MMXや3D Now!と違ってFPUレジスタとの切替え[emms, femms]は不要ですが, 代わりにOSがSSEレジスタをサポートしている必要があります.
基本的に最下位データのみを操作する命令にはss, 4つの(パックされた)データを操作する命令にはpsがつきます. 大抵の命令はssよりはpsの方がレイテンシ, スループットともに1クロックずつ余計にかかります.
xmm?と書くのは面倒なのでxm?と書きます.
4.1 読み込み / 書き込み
4つのデータの読み書きをします. movapsはメモリに対しては16byteアライメントされたデータでないと例外が発生します. どうしてもアライメント出来ない場合はmovupsを使います. 他の演算命令ではアライメントされていないメモリにアクセスすると例外が発生します.
キャッシュを通さずにメモリに書き込みたい時に使います.
レジスタの上位または下位二つのデータとメモリ間との移動を行います. 下図ではpをfloat型変数2個の配列へのポインタとします.
レジスタ間移動はできません.
movhps xm0, [mem64] |
---|
レジスタ | 内容 |
xm0 | * | * | F1 | F0 |
↓ |
xm0 | p[1] | p[0] | F1 | F0
|
movlps xm0, [mem64] |
---|
レジスタ | 内容 |
xm0 | F3 | F2 | * | * |
↓ |
xm0 | F3 | F2 | p[1] | p[0]
|
それぞれhighからlowへ, lowからhighへ2つのデータの移動を行います.
movhlps xm0, xm1 |
---|
レジスタ | 内容 |
xm0 | F3 | F2 | * | * |
xm1 | F3' | F2' | * | * |
↓ |
xm0 | F3 | F2 | F3' | F2'
|
movlhps xm0, xm1 |
---|
レジスタ | 内容 |
xm0 | * | * | F1 | F0 |
xm1 | * | * | F1' | F0' |
↓ |
xm0 | F1' | F0' | F1 | F0
|
1つのデータの移動を行います.
movss xm0, xm1 |
---|
レジスタ | 内容 |
xm0 | F3 | F2 | F1 | * |
xm1 | * | * | * | F0' |
↓ |
xm0 | F3 | F2 | F1 | F0'
|
movss xm0, [mem32] |
---|
レジスタ | 内容 |
xm0 | * | * | * | * |
[mem32] | * | * | * | F0' |
↓ |
xm0 | 0 | 0 | 0 | F0'
|
メモリ/メモリ間とメモリ/レジスタ間とで挙動が違う事に十分注意します. メモリから読み込んだ場合は上位データを0クリアします. レジスタ間では0クリアしません.
これはMMX命令の movd が常に0クリアしていたのとも違います. うっかりするとハマリます.
4.2 足し算 / 引き算 / 掛け算 / 割り算
addps, subps, mulps, divps addss, subss, mulss, divss |
パック演算で足し算/引き算は4-2サイクル, 掛け算は5-2サイクルです. 割り算は数十サイクル[36〜58]かかります.
割り算は3D Now!と同じように逆数命令を使うべきです.
4.3 比較[その1]
実際にはこれらの命令はアセンブラにより3オペランドを持つcmpps, cmpss命令へ展開されます.(3-1サイクル)
??に入る命令 | C言語表記 | 動作内容 |
eq | == | 等しい |
lt | < | 小さい |
le | <= | 等しいか小さい |
unord | 無し | 普通の数[計算できる数]でない |
neq | != | 等しくない |
nlt | >= | 等しいか大きい |
nle | > | 大きい |
ord | 無し | 普通の数
|
条件が成立した時各データが-1になります. unord, ordは普通の数かどうかを調べます.
その後movmskpsを使って整数レジスタにマスク情報を代入できます.
[F3:F2:F1:F0]のそれぞれの最上位bitがreg32の下位4bitに代入され上位28bitは0にクリアされます. (4-1サイクル)
4.4 比較[その2]
最下位データの比較をしてEFLAGSレジスタのZF, PF, CFを変化させます. AF, SF, OFは常に0になります. (1-1サイクル)
比較結果 | フラグ変化 |
- | ZF | PF | CF |
順序なし | 1 | 1 | 1 |
< | 0 | 0 | 1 |
> | 0 | 0 | 0 |
= | 1 | 0 | 0
|
comissとucomissの違いは無効数値例外の発生条件です. (ここでは無視します)
4.5 逆数
rcpps, rcpssは11bitの精度を持つ逆数を求める命令です. (2-2サイクル)
もっと精度がいる場合は
movaps xm0,[data]
rcpps xm1,xm0
mulps xm0,xm1
mulps xm0,xm1
addps xm1,xm1
subps xm1,xm0
とします. スカラー演算の場合は`ps'ではなく`ss'を使えば速くなります.
x0=xの逆数の初期値
xの逆数 = 2 * x0 - x * x0 * x0
で近似計算[22bit]を行っています. ちなみにdivpsは23bitです.
4.6 平方根
23bitの精度を持つ平方根を求める命令です. 精度が必要でない場合は逆数平方根命令を使うべきです.
4.7 平方根の逆数
11bitの精度を持つ平方根の逆数を求める命令です.
もっと精度がいる場合は
movaps xm0, [data]
rsqrtps xm1, xm0
mulps xm1, xm0
mulps xm1, xm0
mulps xm1, xm0
mulps xm1, [Q_m0p5] ; -0.5
mulps xm0, [Q_1p5] ; 1.5
addps xm0, xm1
とします.
x0 = xの逆数平方根の初期値
1/√x = 3/2 * x0 - 1/2 * a * x0^3
で近似計算を行っています.
[参考]概算の方法
4.8 最大 / 最小
maxps / minps, maxss / minss |
説明不要でしょう. それぞれ4-2サイクル, 3-1サイクルです.
4.9 実数⇔整数
丸めモードに従って変換するタイプ(cvt)と切り捨てによって変換するタイプ(cvtt)があります. (だいたい3-1サイクル)
丸めモードを変更するには ldmxcsr を使います. 詳細はマニュアルを見てください.
実数⇒整数
cvt[t]ps2pi mm?, xm? cvt[t]ss2si reg32, xm? |
ps2piはxm?レジスタの下位2つのデータからmm?レジスタへの変換,
ss2siはxm?レジスタの最下位データから整数レジスタへの変換です.
整数⇒実数
mm?レジスタからxm?レジスタの下位2つのデータへの変換です.
整数レジスタからxm?レジスタの最下位データへの変換です.
4.10 ビット演算
andps, andnps, orps, xorps |
andn src1,src2 は src1 =(~src1) & src2です. (2-2サイクル)
4.11 パック演算
punpck{l,h}wd相当の命令です. (3-2サイクル)
unpcklps xm0,xm1 |
---|
レジスタ | 内容 |
xm0 | * | * | F1 | F0 |
xm1 | * | * | F1' | F0' |
↓ |
xm0
| F1' | F1 | F0' | F0
|
unpckhps xm0,xm1 |
---|
レジスタ | 内容 |
xm0 | F3 | F2 | * | * |
xm1 | F3' | F2' | * | * |
↓ |
xm0
| F3' | F3 | F2' | F2
|
4.12 シャッフル演算
4つのデータの入れ替えを行います. (2-2サイクル)
PACK(a,b,c,d)=(a)*64+(b)*16+(c)*4+(d)とすると(a,b,c,dは2bitデータ)
shufps xm0, xm1, PACK(a,b,c,d) |
---|
レジスタ | 内容 |
xm0 | F3 | F2 | F1 | F0 |
xm1 | F3' | F2' | F1' | F0' |
↓ |
xm0 | Fa' | Fb' | Fc | Fd
|
例えば
shufps xm0, xm0, 0
でxm0の最下位データを全体にコピーします.
MMX2 命令の pshufw とは挙動が違うのに注意してください.
御意見は光成滋生<herumi@nifty.com>までお願いします