4. SSE

float型浮動小数を4つ並列に演算する機構です. MMXや3D Now!の様にFPUレジスタを利用しているのではなく全く新規に作られたレジスタです. そのため, MMXや3D Now!と違ってFPUレジスタとの切替え[emms, femms]は不要ですが, 代わりにOSがSSEレジスタをサポートしている必要があります.

基本的に最下位データのみを操作する命令にはss, 4つの(パックされた)データを操作する命令にはpsがつきます. 大抵の命令はssよりはpsの方がレイテンシ, スループットともに1クロックずつ余計にかかります.

  • 以下, 表の中の`F'はfloat型データ1つを表します.
  • `a-bサイクル'はレイテンシa, スループットbを表します.
  • xmm?と書くのは面倒なのでxm?と書きます.

    4.1 読み込み / 書き込み

    movaps, movups
    4つのデータの読み書きをします. movapsはメモリに対しては16byteアライメントされたデータでないと例外が発生します. どうしてもアライメント出来ない場合はmovupsを使います. 他の演算命令ではアライメントされていないメモリにアクセスすると例外が発生します.

    movntps [mem128],xm?
    キャッシュを通さずにメモリに書き込みたい時に使います.

    movhps, movlps
    レジスタの上位または下位二つのデータとメモリ間との移動を行います. 下図ではpをfloat型変数2個の配列へのポインタとします.
    レジスタ間移動はできません.

    movhps xm0, [mem64]
    レジスタ内容
    xm0**F1F0
    xm0p[1]p[0]F1F0

    movlps xm0, [mem64]
    レジスタ内容
    xm0F3F2**
    xm0F3F2p[1]p[0]

    movhlps, movlhps
    それぞれhighからlowへ, lowからhighへ2つのデータの移動を行います.

    movhlps xm0, xm1
    レジスタ内容
    xm0F3F2**
    xm1F3'F2'**
    xm0F3F2F3'F2'

    movlhps xm0, xm1
    レジスタ内容
    xm0**F1F0
    xm1**F1'F0'
    xm0F1'F0'F1F0

    movss
    1つのデータの移動を行います.

    movss xm0, xm1
    レジスタ内容
    xm0F3F2F1*
    xm1***F0'
    xm0F3F2F1F0'


    movss xm0, [mem32]
    レジスタ内容
    xm0****
    [mem32]***F0'
    xm0000F0'

    メモリ/メモリ間とメモリ/レジスタ間とで挙動が違う事に十分注意します. メモリから読み込んだ場合は上位データを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]

    cmp??ps, cmp??ss
    実際にはこれらの命令はアセンブラにより3オペランドを持つcmpps, cmpss命令へ展開されます.(3-1サイクル)

    ??に入る命令C言語表記動作内容
    eq==等しい
    lt<小さい
    le<=等しいか小さい
    unord無し普通の数[計算できる数]でない
    neq!=等しくない
    nlt>=等しいか大きい
    nle>大きい
    ord無し普通の数

    条件が成立した時各データが-1になります. unord, ordは普通の数かどうかを調べます.

    その後movmskpsを使って整数レジスタにマスク情報を代入できます.
    movmskps reg32,xm0
    [F3:F2:F1:F0]のそれぞれの最上位bitがreg32の下位4bitに代入され上位28bitは0にクリアされます. (4-1サイクル)

    4.4 比較[その2]

    comiss, ucomiss
    最下位データの比較をしてEFLAGSレジスタのZF, PF, CFを変化させます. AF, SF, OFは常に0になります. (1-1サイクル)

    比較結果フラグ変化
    -ZFPFCF
    順序なし111
    <001
    >000
    =100

    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 平方根
    sqrtps, sqrtss
    23bitの精度を持つ平方根を求める命令です. 精度が必要でない場合は逆数平方根命令を使うべきです.

    4.7 平方根の逆数
    rsqrtps, rsqrtss
    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?レジスタの最下位データから整数レジスタへの変換です.

    整数⇒実数
    cvtpi2ps xm?, mm?
    mm?レジスタからxm?レジスタの下位2つのデータへの変換です.
    cvtsi2ss xm?, reg32
    整数レジスタからxm?レジスタの最下位データへの変換です.

    4.10 ビット演算

    andps, andnps, orps, xorps
    andn src1,src2 は src1 =(~src1) & src2です. (2-2サイクル)

    4.11 パック演算

    unpcklps, unpckhps
    punpck{l,h}wd相当の命令です. (3-2サイクル)

    unpcklps xm0,xm1
    レジスタ内容
    xm0**F1F0
    xm1**F1'F0'
    xm0 F1'F1F0'F0

    unpckhps xm0,xm1
    レジスタ内容
    xm0F3F2**
    xm1F3'F2'**
    xm0 F3'F3F2'F2

    4.12 シャッフル演算

    shufps xm0, xm1, imm8
    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)
    レジスタ内容
    xm0F3F2F1F0
    xm1F3'F2'F1'F0'
    xm0Fa'Fb'FcFd

    例えば
    shufps xm0, xm0, 0
    
    でxm0の最下位データを全体にコピーします.
    MMX2 命令の pshufw とは挙動が違うのに注意してください.
    御意見は光成滋生<herumi@nifty.com>までお願いします