Nanashi-softプログラマ専用PS3 Linux SDL


◇PS3 Linux SDLでプログラミング -SIMDで半透明処理する-

通常のプログラムを SIMDを使うように書き換えてみます
グラフィックの一部に半透明な四角を描く例です

○元のプログラム

1920×1080の画面の左上 640×480に黄色い半透明な四角を描いてみます
普通にプログラミングすると、こんな感じになります

unsigned char rgbdata[1920*1080*3]; //これが元データとする

int x, y;
for(y=0; y < 480; y++){
  for(x=0; x < 640; x++){
    int r, g, b;
    r = rgbdata[y*1920*3 + x*3];
    r = (r + 255) >> 1;
    g = rgbdata[y*1920*3 + x*3 +1];
    g = (g + 255) >> 1;
    b = rgbdata[y*1920*3 + x*3 +2];
    b = b >> 1;
  }
}


わざと単純に書いています
SIMD命令に割り算が無いので、 / 2では無く >> 1を使用しています

○SIMD向けロジックに変更

基本として、SIMDは同時に演算を行う事で高速化されます
RGB3つを同時にベクター演算できるように考えます

まず、r,g,bの代入を始めにまとめて行っても同じですので、移動します

for(y=0; y < 480; y++){
  for(x=0; x < 640; x++){
    int r, g, b;
    r = rgbdata[y*1920*3 + x*3];
    g = rgbdata[y*1920*3 + x*3 +1];
    b = rgbdata[y*1920*3 + x*3 +2];

    r = (r + 255) >> 1;
    g = (g + 255) >> 1;
    b = b >> 1;
  }
}


r,g,bの計算部分なのですが、SIMD命令には足してシフトするを同時に行う命令はありません
そこで、足し算とシフト演算を分離します
r = (r + 255) >> 1;
g = (g + 255) >> 1;
b = b >> 1;

r = r + 255;
r = r >> 1;
g = g + 255;
g = g >> 1;
b = b >> 1;

そして、足し算は先に全て行っておきます
r = r + 255;
g = g + 255;
r = r >> 1;
g = g >> 1;
b = b >> 1;

この計算を、SIMDで行うのですが、最初の足し算に bがありません
ベクター変数は原則として、4ついっぺんにしか計算できませんので、bには 0を足す事にします
r = r + 255;
g = g + 255;
b = b + 0;
r = r >> 1;
g = g >> 1;
b = b >> 1;

プログラムをまとめるとこんな感じです

for(y=0; y < 480; y++){
  for(x=0; x < 640; x++){
    int r, g, b;
    r = rgbdata[y*1920*3 + x*3];
    g = rgbdata[y*1920*3 + x*3 +1];
    b = rgbdata[y*1920*3 + x*3 +2];

    r = r + 255;
    g = g + 255;
    b = b + 0;

    r = r >> 1;
    g = g >> 1;
    b = b >> 1;
  }
}


このように SIMD向けにロジックを変更する必要があるのが難点です

○VMX命令に置き換える

代入を VMXで記述します。SIMDで計算するで書いたようにポインタを使って直接代入します

for(y=0; y < 480; y++){
  for(x=0; x < 640; x++){
    vector int vrgb; //vector変数を宣言
    int *pvrgb = (int*)&vrgb; //vector変数のポインタ取得
    pvrgb[0] = rgbdata[y*1920*3 + x*3];
    pvrgb[1] = rgbdata[y*1920*3 + x*3 +1];
    pvrgb[2] = rgbdata[y*1920*3 + x*3 +2];
    pvrgb[3] = 0; //1つ余るので0で埋めておく

    r = r + 255;
    g = g + 255;
    b = b + 0;

    r = r >> 1;
    g = g >> 1;
    b = b >> 1;
  }
}


足し算を vec_add、右シフトを vec_sraに置き換えます

for(y=0; y < 480; y++){
  for(x=0; x < 640; x++){
    vector int vrgb; //vector変数を宣言
    int *pvrgb = (int*)&vrgb; //vector変数のポインタ取得
    pvrgb[0] = rgbdata[y*1920*3 + x*3];
    pvrgb[1] = rgbdata[y*1920*3 + x*3 +1];
    pvrgb[2] = rgbdata[y*1920*3 + x*3 +2];
    pvrgb[3] = 0; //1つ余るので0で埋めておく

    //+255の足し算
    vector int vplus = {255, 255, 0, 0};
    vrgb = vec_add(vrgb, vplus);

    //右シフト演算
    vrgb = vec_sra(vrgb, vec_splat_u32(1));

    //元の変数に戻す
    rgbdata[y*1920*3 + x*3] = pvrgb[0];
    rgbdata[y*1920*3 + x*3 +1] = pvrgb[1];
    rgbdata[y*1920*3 + x*3 +2] = pvrgb[2];
  }
}

○まとめ

ここで説明した方法は、本来の SIMDプログラミングとは異なります
元データを行列変換して一気に 4ドット分ずつ計算するような方法が大抵の解説では行われています
ですが、それだと大幅にロジックを変えなければならず、ほんの少し高速化したいだけの時に不便ですし、ロジックが変わるとバグ混入の原因にもなります

これだけの事でも 1000ミリ秒かかっていた計算が 600ミリ秒に縮まった事もありました
一度に 4つの演算を行う速度向上もありますが、手作業で最適化を行った事にもなる為です

ちなみに、数値をベクタ変数で宣言を行うだけでもコンパイラに最適化ヒントを与えた事になり、高速化した事もありました
どのような VMX命令を出せば良いのか分からない時に使えそうな方法です


TOPプログラマ専用PS3 Linux SDL