SSE2命令(pxor)を使用した高速XOR演算プログラム

SSE2では、128ビット整数演算のSIMD命令の拡張が行われており、たとえば、pxor命令を使うことで128ビット単位でXORの演算を行うことができます。

pxor命令を使って128ビット単位で演算を行うには、SSE/SSE2用のxmmレジスタにmovdqu/movdqa命令(「SIMD拡張 命令を使用したメモリ間転送プログラム」で解説)でデータをメモリから転送し、pxor命令を呼ぶことで実行できます。pxor命令は、指定されたレジスタが mmx(64ビット)かxmm(128ビット)かを判断して演算を行ってくれます。以下がpxor命令を使ったXOR演算のソースコードになります。SSE2非対応の場合を想定して、longで演算を行う部分も書いてま す。#define SSE2をコメントアウトするとlong演算になります。

ソースコード

#include
#include

#define SSE2
//#define DEBUG

void SIMD_XOR(void *, void *, void *, size_t);

// SSE2のpxor命令を用いて128bitずつXORの演算を行う
void SIMD_XOR(void *src1, void *src2, void *dst, size_t size)
{
  unsigned int long_size, op_cnt;
  unsigned long t;
  unsigned long *src1_lp, *src2_lp, *dst_lp;
  unsigned char *src1_cp, *src2_cp, *dst_cp;

  src1_cp=(unsigned char *)src1;
  src2_cp=(unsigned char *)src2;
  dst_cp=(unsigned char *)dst;

  #ifdef SSE2

    // SSE2命令を用いて128bitずつXORを計算する
    #ifdef DEBUG
    printf("SSE2 Mode.\n");
    #endif

    op_cnt=(int)(size/16);

    asm volatile ("\n"
    "    mov      %3, %%eax\n"                // op_cntをEAXレジスタに代入
    "  calc:\n"
    "    movdqu   (%0),%%xmm0\n"         // src1のアドレスから128ビット分xmm0レジスタに転送
    "    movdqu   (%1),%%xmm1\n"         // src2のアドレスから128ビット分xmm1レジスタに転送
    "    pxor     %%xmm1,%%xmm0\n"     // xmm0とxmm1のXORを計算
    "    movdqu   %%xmm0,(%2)\n"         // 計算結果(xmm0)をdstに転送

    "    sub      $1, %%eax\n"                  // op_cntを減産
    "    cmp      $0, %%eax\n"                 // 0であれば終了
    "    je       end\n"

    "    add      $16, %0\n"                      // src1, src2, dstのアドレスを128ビット分進める
    "    add      $16, %1\n"
    "    add      $16, %2\n"
    "    jmp      calc\n"

    "  end:\n"
    "    emms\n"                                     // MMX命令終了
    :
    : "r" (src1), "r" (src2), "r" (dst), "r" (op_cnt)  : "%eax"
    );

    // 残った分を1バイトずつ計算
    for(t=op_cnt*16; t<size; t++){
      *((char *)dst_cp+t) = *((char *)src1_cp+t) ^ *((char *)src2_cp+t);
    }
    
  #else
    // longでXORを計算する

    long_size=sizeof(long);

    #ifdef DEBUG
    printf("Size of long : %d bytes\n", long_size);
    #endif

    // longのポインタに変換
    src1_lp=(unsigned long *)src1;
    src2_lp=(unsigned long *)src2;
    dst_lp=(unsigned long *)dst;
    
    // longの単位でXORを計算
    op_cnt=(int)(size/long_size);
    for(t=0;t<op_cnt;t++){
      (long)*(dst_lp+t) = (long)*(src1_lp+t) ^ (long)*(src2_lp+t);
    }
    
    // 残った分を1バイトずつ計算
    for(t=op_cnt*long_size; t<size; t++){
      *(dst_cp+t) = *(src1_cp+t) ^ *(src2_cp+t);
    }
  #endif
}

int main(void)
{
  unsigned char src1[30000], src2[30000], dst[30000];
  unsigned int t, u;

  for(t=0;t<30000;t++){
    src1[t]=255;
    src2[t]=15;
  }

  for(u=0; u<2000000; u++){
    SIMD_XOR(src1, src2, dst, 30000);
  }

  #ifdef DEBUG
  for(t=0;t<30000;t++)
    printf("%d\n", dst[t]);
  #endif
}

(ファイル)
redundant.cpp

(性能)

上記のソース、もともと使っていたものを少し修正したのですが、そのためか以前測定したときよりも性能差があまりでなくなってしまってます。なので、どこかおかしなところがあるかもしれません。ちなみに、Xeon 2.8GHzのマシンでSSE2とlong演算の比較をしたところ、30,000byteのデータのXOR演算を200万回行ったときの時間差は、

・SSE2 22秒
・long  33秒

になりました。以前はもっとSSE2が圧倒的に速かったのですが、もしかすると測定したマシンが違うせいかもしれません。longが以前より短時間 で終わっているので、マシン性能が上がったためlongとSSE2の差が縮まったのかも。

トラックバック(0)

トラックバックURL: http://kirihari.net/mt/mt-tb.cgi/6

コメントする