No Such Blog or Diary

«Prev || 1 | 2 | 3 |...| 1230 | 1231 | 1232 |...| 1349 | 1350 | 1351 || Next»

gwigle で遊ぶ

The Gwigle Game という google の検索結果から検索に使った言葉を答えるゲームをやった.とりあえず final と書かれたページまで行ったので一通り正解し終えた気がする.面倒というか分かりにくかったのは株の情報から検索ワードを当てさせる問題とか「もしかして」で訂正される前の間違った綴りを当てさせる問題とかで,これらのいやらしい問題もあって結局二日かかってしまった...

GWTで

Drag and Drop が標準では実装されてない.マウスイベント取れるから自前で実装できるけれど面倒.Developper forum のほうにコードがあるからもって来ればいいのだがマウスを押したとこからドラッグが始まってくれてもちょっと困るので... 毎度のごとく多少動いたらドラッグ開始に書き換えねば.ついでにドラッグしてるものは半透明にしたいので...

 filter: alpha(opacity=25);
 -moz-opacity:0.25;
 opacity:0.25;

あたりの設定を style に入れときゃいいのかな.先は長い.

SSE2 を使う

更なる高速化のためにSSE2に手を出してみる.CIPスキームの最内ループの元がこんなもん:

  double const *v = VV + (La+Lb)*(k-1) + La;
  double *v2 = VV + (La+Lb)*(k-1);
  for(double const*const ve = VV + (La+Lb)*(k-1) + La + STEPLEN; ve != v; v2+=2, v+=2){
    v2[0] = CA2 * v[-2] + CA1 * v[-1] + C0 * *v + CB1 * v[1];
    v2[1] = DA2 * v[-2] + DA1 * v[-1] + D0 * *v + DB1 * v[1];
  }

配列をインターリーブして使ってるが,長さ 2 のベクトルを一つ左の値と現在地の値で更新している.ちょうど 128bit のデータになってるから SSE2 にぴったりなので,SSE2を使ったコードが下.当然のごとく配列の alignment を 16byte に合わせておく.

__m128d A2 = _mm_setr_pd(CA2, DA2);
__m128d A1 = _mm_setr_pd(CA1, DA1);
__m128d A0 = _mm_setr_pd(C0 , D0 );
__m128d B1 = _mm_setr_pd(CB1, DB1);
double const *v = VV + (La+Lb)*(k-1) + La;
double *v2 = VV + (La+Lb)*(k-1);
__m128d pv = _mm_load_pd(v-2);
for(double const*const ve = VV + (La+Lb)*(k-1) + La + STEPLEN; ve != v; v2+=2, v+=2){
  __m128d r = _mm_shuffle_pd(pv,pv,_MM_SHUFFLE2(0,0));
  __m128d op = _mm_shuffle_pd(pv,pv,_MM_SHUFFLE2(1,1));
  r = _mm_mul_pd(r, A2);
  op = _mm_mul_pd(op, A1);
  r = _mm_add_pd(r,op);
  pv = _mm_load_pd(v);
  op = _mm_shuffle_pd(pv,pv,_MM_SHUFFLE2(0,0));
  op = _mm_mul_pd(op, A0);
  r = _mm_add_pd(r,op);
  op = _mm_shuffle_pd(pv,pv,_MM_SHUFFLE2(1,1));
  op = _mm_mul_pd(op, B1);
  r = _mm_add_pd(r,op);			
  _mm_store_pd(v2, r);
}

xmm レジスタがもう一本あればもう少しスマートなのだが... 上のコードより場合によっては下のほうが速い.アセンブラ上は定数のうち一つをメモリから取るようになるが,並列に実行できる命令が増えるので速いのかな?

  __m128d op1 = _mm_shuffle_pd(pv,pv,_MM_SHUFFLE2(0,0));
  __m128d op2 = _mm_shuffle_pd(pv,pv,_MM_SHUFFLE2(1,1));
  op1 = _mm_mul_pd(op1, A2);
  op2 = _mm_mul_pd(op2, A1);
  __m128d r = _mm_add_pd(op1,op2);
  pv = _mm_load_pd(v);
  op1 = _mm_shuffle_pd(pv,pv,_MM_SHUFFLE2(0,0));
  op2 = _mm_shuffle_pd(pv,pv,_MM_SHUFFLE2(1,1));
  op1 = _mm_mul_pd(op1, A0);
  op2 = _mm_mul_pd(op2, B1);
  op1 = _mm_add_pd(op1,op2);
  r = _mm_add_pd(r,op1);

うーむ,ここら辺になると Pentium4 の仕様を良く調べないと全く分からない.

SSE2 を C++ で使いたい

次なる最適化は SSE2 の SIMD 命令の使用だ,と.128bit レジスタが8本使えるからいろいろレジスタだけで出来そう.調べてみると xmmintrin.h, emmintrin.h, pmmintrin.h が SSE, SSE2, SSE3 を使うためのヘッダファイルらしい.インテルのコンパイラのリファレンスを見ないと細かい部分がどう使うのか良くわからないが使えるものは使ってみよう.ちなみに g++ に -msse2 のオプションをつけないと怒られた.

で,一般化

ある点の更新に左右の固定範囲の点の値を使う場合ようなスキーム全般に対して,先の例と同様のキャッシュを効くようにするループ変形を可能とする方法を思いついた.

ということで,とある人間が書いたCIPスキームとやらのプログラムを変形してみた.結果としてメモリを少々余計に喰うがやっぱ3倍速くなった.さて,忘れないうちに原理をまとめとくか.

キャッシュはやはり有効だった

キャッシュにヒットし続けるようにループを分割してみた(簡単のため配列の使い方も少し変えてる).3倍から5倍程度速くなった.あとは sse2 の命令とか使ってみたいところだけどうまく使える形にプログラムを変形できない… むりかなぁ?

初期の単純なネストループ:

        for(int p = R; p >0; p--) {
            int q = (ISROOT()) ? p-1: 0;
            double *u = U+q;
            double *v = u++;
            register double cv = *v;
            for(double const *e = U+Len+p; u != e; u++,v++){
                register const double cu = *u;
                *v = (1 - MU) * cu + MU * cv;
                cv = cu;
            }
        }

内側のループを分割してキャッシュ上のデータにアクセスしまくる:

        double *uu = U;
        for(int p = R; p >0; p--) {
            int q = (ISROOT()) ? p-1: 0;
            double *u = uu+q;
            double *v = u++;
            register double cv = *v;
            for(double const *e = uu+p; u != e; u++,v++){
                register const double cu = *u;
                *v = (1 - MU) * cu + MU * cv;
                cv = cu;
            }
        }
        double const *uue = uu + Len - STEPLEN;
        // iteration - STEPLEN * floor(Len/STEPLEN) steps
        for(; uu < uue; uu+=STEPLEN) {
            for(int p = R; p >0; p--) {
                double *u = uu+p-1;
                double *v = u++;
                register double cv = *v;
                for(double const *e = uu+STEPLEN+p; u != e; u++,v++){
                    register const double cu = *u;
                    *v = (1 - MU) * cu + MU * cv;
                    cv = cu;
                }
            }
        }
        // last - rest steps
        int rlen = Len - STEPLEN * (Len/STEPLEN);
        for(int p = R; p >0; p--) {
            double *u = uu+p-1;
            double *v = u++;
            register double cv = *v;
            for(double const *e = uu+rlen+p; u != e; u++,v++){
                register const double cu = *u;
                *v = (1 - MU) * cu + MU * cv;
                cv = cu;
            }
        }
«Prev || 1 | 2 | 3 |...| 1230 | 1231 | 1232 |...| 1349 | 1350 | 1351 || Next»
Search
Feeds

Page Top