GPUを手っ取り早く活用するためのライブラリ、ArrayFireのご紹介 gfor編(みかん)
この記事はGPGPU Advent Calendarの12月10日、10日目の記事です。日付を豪快にぶっちしそうですが10日目の記事です
俺「今日俺の番なんですよぉお」
先輩「どうすんの? ってか俺絶対3日とか書かないからね!」
という会話が先ほど帰り際にありました(実話
我が名はインフィニティ―…無限のメモリーなり…
さて、ArrayFireですが、前回は行列の演算までやりました。
続いて、ArrayFire(Jacketも、ですが) の特徴的なgfor構文についてみていきたいと思います。
gfor?
gfor構文とは、forループを展開するものです。
はい、何のことを言ってるかわからないですね。アンロールでもするの? いえいえ、違います。
百聞は一見にしかずなので、次のコードをご覧ください。
const size_t num = 1024; af::array A(num); for(int i = 0; i < num; ++i) { A(i) = i; } gfor(af::array i, num) { A(i) = i; }
forがgforに置き換わっていて、中が見慣れないものになっています。
これは、今Aぶっ壊していますが、どちらも同じものが入ります。
本当にそうなるか?
gforに変えると何かいいことがあるのか?
それを検証するのが次のコードです
#include <arrayfire.h> #include <iostream> int main() { const size_t num = 1024; af::array A = af::seq(static_cast<double>(num)); af::timer::start(); for(size_t i = 0; i < num; ++i) { A(i) = A(i) + 1; } double time_for = af::timer::stop(); std::cout << "for time: " << time_for << "sec. " << std::endl; af::array B = af::seq(static_cast<double>(num)); af::timer::start(); gfor(af::array i, num) { B(i) = B(i) + 1; } double time_gfor = af::timer::stop(); std::cout << "gfor time: " << time_gfor << "sec. " << std::endl; af::print(A); af::print(B); return 0; }
最初の段階では、A,Bにはそれぞれ、0から1023までの値が代入されています。
af::array A = af::seq(static_cast<double>(num)); (中略) af::array B = af::seq(static_cast<double>(num));
この部分で初期化を行っているわけです。
for,gforの中で、それぞれイテレータを足しているので、1から1024までの値が入っていることになります
中身の出力は割合しますが、時間だけを抜き出して表示すると
for time: 2.05661sec. gfor time: 0.000577sec.
と、このように、実に3000倍!? おいこれ計算間違ってんだろ!?
……?いや、間違って……ない……?
……とまぁ、超高速。あまりに高速すぎて訝しむぐらいには。現在絶賛訝しみ中。
gforには、いくつか書き方があります。
gfor(var, n); gfor(var, first, last); gfor(var, first, increment, last);
varというのが、いわゆるsize_t i = 0とか、お約束のように書くイテレータに相当します
af::seqが使用され、一番上の場合は0,1,...,n-1までが生成されます。
二つ目は、最初と最後が指定できる形式です。first,first+1,...,last-2,last-1までが生成されます
三つ目は、二つ目に加えて増加量を指定したものです。first+increment, first+increment*2,...,last-increment*(n-1)までが生成されます
gforはえー!といいたいところですが、これだけじゃあまり実用性が…
こっちが作った関数を呼びたいとか、そういうのが人のサガというものです。これが ひとの サガ か
#include <arrayfire.h> #include <iostream> af::array mul(af::array A, af::array B) { af::array ret; ret = A * B; return ret; } int main() { const size_t num = 4; af::array A = af::randu(num, num); af::array B = af::randu(num, num); af::array res = af::constant(0, num, num); try { gfor(af::array k, num) { res(af::span, k) = mul(A(af::span, k), B(af::span, k)); } } catch (af::exception& e) { std::cout << e.what() << std::endl; } af::print(A); af::print(B); af::print(res); return 0; }
ただ乗算するだけの関数ですが、gforの内部で呼び出しています。
ここで見慣れないaf::spanなるものが出てきていますが、これは、「その次元すべて」を意味します。
A = 0.7402 0.9251 0.4702 0.7140 0.9210 0.4464 0.5132 0.3585 0.0390 0.6673 0.7762 0.6814 0.9690 0.1099 0.2948 0.2920 B = 0.3194 0.2080 0.2343 0.5786 0.8109 0.6110 0.8793 0.5538 0.1541 0.3073 0.6462 0.3557 0.4452 0.4156 0.9264 0.7229 res = 0.2364 0.1924 0.1102 0.4131 0.7468 0.2727 0.4513 0.1986 0.0060 0.2051 0.5016 0.2424 0.4313 0.0457 0.2731 0.2111
結果はこんな感じで、乗算がなされていることが確認できます。
ユーザー関数も呼べるんだ!!
ただし注意点があります。
gforで実行される関数の内部でifで分岐するようなコードを書くことはできません。
af::array mul(af::array A, af::array B, float t) { af::array ret; if(t > 0.0f) { ret = A * B; } return ret; }
こんなコードを書くとエラー出ます。
というか、gfor内部での条件分岐はエラーが出ます。
回避するためには投機的実行っぽい感じにしなければなりません。どういうこと?
どういうことかは待て次回?