10
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

CUDA & OpenCLAdvent Calendar 2014

Day 5

WebCLプログラミング入門

Last updated at Posted at 2014-12-04

はじめに

CUDA & OpenCL Advent Calendar 2014の5日目です。
前回、WebCLのデモを動かすでWebCLの環境を導入して動作確認をしましたので、今回はWebCLのプログラムを少し書いてみたいと思います。
Local Memoryの使用など程よくテクニカルな行列積をお題にしようと思います。
なお、OpenCLの知識は前提とさせてもらいます。

PlatformとDeviceの取得

WebCLでは通常のOpenCLと同様に、動作させている環境で使用可能な全てのOpenCL Deviceを選択して使用することができます。
複数のOpenCL Platformがインストールされている場合ももちろん全て使用することができます。

早速、PlatformとDeviceを取得するコードを書いてみます。

function getDevices() {
  var result = [];
  webcl.getPlatforms().forEach(function(platform) {
    platform.getDevices().forEach(function(device) {
      result.push([platform, device]);
    });
  });
  return result;
}

WebCL対応の環境であれば、webclというオブジェクトがグローバルにアクセスできるようになります。
webcl.getPlatforms()でプラットフォームの配列を取得できます。
取得したplatformからplatform.getDevices()でデバイスの配列を取得します。
上のコードでは、[platform, device]というペアの配列を作成しています。

Contextの取得

お次はContextです。

var context;
context = webcl.createContext(); // default context
context = webcl.createContext(device);

webcl.createContext(device)を呼び出すだけです。
引数のdeviceを与えると、デフォルトのDeviceが選択されます。

Kernelの取得

WebCLでのDevice側のプログラムは、OpenCLと同じくOpenCL C言語がそのまま使われます。
行列積を実装するにあたって、カーネル関数はNVIDIAのサンプルからそのまま持ってきてみましょう。
HTMLまたはJSのソース中に埋め込んでもいいですし、AJAXで取得するようにしても構いません。

カーネル関数のソースコードを文字列としてkernelSourceに取得できたら、以下のようにしてKernelを取得します。

var program = context.createProgram(kernelSource);
program.build([device], '-D BLOCK_SIZE=16');
var kernel = program.createKernel('matrixMul');

context.createProgram(kernelSource)でProgramを取得し、program.build(devices, options)でビルドします。
今回持ってきたカーネル関数ではBLOCK_SIZEという定数が必要になっていますので、optionsに渡すことで対応しています。
あとは、必要なKernelをprogram.createKernel(kernelName)で取得します。
ちなみに、OpenCLでのclCreateProgramWithBinaryみたいなものは無いようです。

Bufferの作成

デバイス側のメモリを用意します。

var devA = context.createBuffer(WebCL.MEM_READ_ONLY, bufASize);
var devB = context.createBuffer(WebCL.MEM_READ_ONLY, bufBSize);
var devC = context.createBuffer(WebCL.MEM_WRITE_ONLY, bufCSize);

context.createBufferでサクッと用意します。
bufSizeはバイト数なので、配列の要素数に型のバイト数をかけておく必要があります。
OpenCLの定数関係はWebCLオブジェクトの中にあります。

また、ホスト側はJavaScriptのTyped Arrayを使います。

var A = new Float32Array(ASize);
var B = new Float32Array(BSize);
var C = new Float32Array(CSize);

CommandQueue

CommandQueueを取得します。

var queue = context.createCommandQueue(device);

ホスト・デバイス間のメモリのやり取りは以下の様な感じになります。

queue.enqueueWriteBuffer(devA, false, 0, bufASize, A);
queue.enqueueWriteBuffer(devB, false, 0, bufBSize, B);

Kernel呼び出し

ようやく準備が整ってきたのでKernelを呼び出します。
まずは、引数をセットします。

kernel.setArg(0, devC);
kernel.setArg(1, devA);
kernel.setArg(2, devB);
kernel.setArg(3, new Uint32Array([floatSize * blockSize * blockSize]));
kernel.setArg(4, new Uint32Array([floatSize * blockSize * blockSize]));
kernel.setArg(5, new Uint32Array([wa]));
kernel.setArg(6, new Uint32Array([hb]));
kernel.setArg(7, new Uint32Array([ha]));

このカーネル関数では、1〜3番目の引数がGlobal Memory、4、5番目がLocal Memory、6〜8番目をPrivate Memoryとして受け取ります。
1〜3番目にはBufferをそのまま渡してやります。
4、5番目では、要素数1のUint32ArrayにLocal Memoryのサイズを入れてやることでそのサイズのLocal Memoryが確保されます。
6〜8番目でも、スカラー値の受け渡しに要素数1のTyped Arrayを使います。

そしていよいよ呼び出しです。

queue.enqueueNDRangeKernel(kernel, 2, null, [wa, hb], [blockSize, blockSize]);
queue.finish();

1番目の引数がKernel、2番目の引数がワークアイテムの次元数、4番目と5番目の引数でそれぞれグローバルとローカルのワークアイテム数を指定します。

デモ

以上のコードを整理してゴチャゴチャ付け足して動かせるようにしました。
http://likr.github.io/webcl20141205/

Block Size * Scale次の正方行列の積を計算します。
Repeat回計算した平均時間を表示します。

比較のために、適当に実装したJSでの行列積も置いておきます。
OpenCL DeviceJSを選ぶとこれになります。

function matMulJS(A, B, C, wa, ha, hb) {
  var i, j, k;
  for (i = 0; i < wa; ++i) {
    for (j = 0; j < hb; ++j) {
      var val = 0;
      var iA = wa * i;
      var iB = j;
      for (k = 0; k < ha; ++k) {
        val += A[iA] * B[iB];
        iA += 1;
        iB += ha;
      }
      C[i * wa + j] = val;
    }
  }
}

申し訳程度に実行結果のスクショを載せておきます。

webcl.png

NVIDIAとIntelのOpenCL Platformがインストールされています。
WebCLはメモリ転送も含めて時間を測っているので、サイズが小さいとJS実装よりも遅くなっているんですが、十分に大きいサイズになると数十倍程度の差がでてきます。

おわりに

今回はようやくまともにWebCLプログラムを書いて実行しましたが、十分に大きいデータの処理ではCPU、GPUともにWebCLで性能的なメリットが有ることが確認できました。
また、OpenCLのカーネル関数が修正もなくそのままWebCLで動かせることも確認できました。
WebCLのAPIはOpenCLと若干名前が違ったりしていますが、OpenCLの経験がある人にとってはそれ程苦労なくWebCLプログラムを書くことができるように思います。
もう一回WebCLについて書く予定ですが、WebGL Resource Sharingあたりを触れたいと思います。

Advent Calendarの次の担当は@duxcaさんです。

10
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?