読者です 読者をやめる 読者になる 読者になる

東方算程譚

επιστημηがヨタをこく、弾幕とは無縁のCUDAなタワゴト

thrust::for_eachによるヒストグラム

Thrust

Thrust、個人的にはかなり気に入ってるライブラリなんだけど、なんだか影が薄いんですよね。 cuFFTやcuDNNなどなど、僕らにはとても書けそうにないガチにヘビィな連中とは異なり、Thrustが提供するのは copy/search/sortといった基本的なアルゴリズム群だからでしょうか。

CUDAが、というかnvccが device lambda をサポートしてくれたおかげで、Thrustの使い心地がぐっと良くなりました。その一例。

ヒストグラム(度数分布表)を作ります。たくさんの数値データがあって、それらのとる値の範囲を等間隔に刻んだスロット列を用意します。各数値をスロットに振り分けたとき、各スロットにいくつずつ納まるかってやつ。

アルゴリズムはこんなカンジ:

  float score[N]; // 数値データ列(input)
  int   hist[S]; // ヒストグラム(output) 初期値はall-0
  for ( score 内の各数値 val について ) {
    if ( valがスロットiの範囲にあるなら } {
      ++hist[i};
    }
  }

これをkernelで実装するのはちっとも難しくありません。けどもたったこれだけのためにわざわざkernel書くのが面倒なわけで、Thrustでさらっと実装してみました。

#include <thrust/device_vector.h>
#include <thrust/for_each.h>

#include <curand.h>

#include <iterator>
#include <iostream>
#include <ctime>

int main() {
  using namespace std;

  const int N = 100000;
  thrust::device_vector<float> dscore(N);
  thrust::device_vector<int>   dhist(101, 0);

  { // 平均 50.0, 標準偏差 15.0 の正規分布乱数で dscoreを埋める 
    curandGenerator_t gen;
    curandCreateGenerator(&gen, CURAND_RNG_PSEUDO_DEFAULT);
    curandSetPseudoRandomGeneratorSeed(gen, static_cast<unsigned long long>(time(nullptr)));
    curandGenerateNormal(gen, dscore.data().get(), N, 50.30f, 15.0f);
    curandDestroyGenerator(gen);
  }

  // dscoreの各値を四捨五入し、0以上/100以下ならばヒストグラムに加える
  int* histPtr = dhist.data().get(); // for_each内のlambdaがコレをキャプチャする
  thrust::for_each(begin(dscore), end(dscore),
                   [=] __device__ (float val) -> void {
                     int i = static_cast<int>(val+0.5f); // 四捨五入して
                     if ( i >= 0 && i <= 100 ) { // 0以上100以下なら、当該スロットを+1する
                       atomicAdd(histPtr+i, 1); // ++histPtr[i] だと data-race起こすよきっと!
                     }
                   });
//*/
  for ( int item : dhist ) {
    cout << item << endl;
  }
//*/
}

thrust::for_each(こっから, ここまで, なんかする) の"なんかする"を devie lambda で与えています。ヒストグラムを求める領域:dhistのポインタをlambda式の値キャプチャで食わせています。

※ device lambdaでキャプチャできるのは値キャプチャだけ。参照キャプチャするとコンパイル・エラーとなります。Hostにある変数の参照(つまりポインタ)をDevice側に持ち込んでも意味ないもんね。

結果をCSVにリダイレクトし、excelに食わせたものがこちらになります。

f:id:Episteme:20161130192402p:plain