node-ffi で OpenCL を使う2 - 演算の実行
「node-ffi で OpenCL を使う」 に続き、Node.js を使って OpenCL の演算を実施してみます。
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20160725/
はじめに
演算の実行には ref-array
モジュールを使った方が便利なため、node-ffi をインストールした環境へ追加でインストールしておきます。
ref-array インストール例
> npm install ref-array
OpenCL の演算実行サンプル
今回は配列の要素を 3乗する OpenCL のコード(以下)を Node.js から実行する事にします。
cube.cl
__kernel void cube( __global float* input, __global float* output, const unsigned int count) { int i = get_global_id(0); if (i < count) { output[i] = input[i] * input[i] * input[i]; } }
サンプルコード概要
上記 cube.cl を実行する Node.js サンプルコードの全体像です。(OpenCL の API は try-finally 内で呼び出しています)
OpenCL 演算の入力値として data
変数の値を使用します。OpenCL のコードはファイルから読み込んで code
変数へ設定しています。
OpenCL API の clCreateXXX
で作成したリソースは clReleaseXXX
で解放するようなので、解放処理を都度 releaseList
へ追加しておき、finally で実行するようにしています。
なお、OpenCL API のエラーコード取得には以下の 2通りがあります。(使用する関数による)
- 関数の戻り値でエラーコードを取得
- 関数の引数(ポインタ)でエラーコードを取得
calc.js (全体)
'use strict'; const fs = require('fs'); const ref = require('ref'); const ArrayType = require('ref-array'); const ffi = require('ffi'); const CL_DEVICE_TYPE_DEFAULT = 1; const CL_MEM_READ_WRITE = (1 << 0); const CL_MEM_WRITE_ONLY = (1 << 1); const CL_MEM_READ_ONLY = (1 << 2); const CL_MEM_USE_HOST_PTR = (1 << 3); const CL_MEM_ALLOC_HOST_PTR = (1 << 4); const CL_MEM_COPY_HOST_PTR = (1 << 5); const intPtr = ref.refType(ref.types.int32); const uintPtr = ref.refType(ref.types.uint32); const sizeTPtr = ref.refType('size_t'); const StringArray = ArrayType('string'); const clLib = (process.platform == 'win32') ? 'OpenCL' : 'libOpenCL'; // 使用する OpenCL の関数定義 const openCl = ffi.Library(clLib, { 'clGetPlatformIDs': ['int', ['uint', sizeTPtr, uintPtr]], 'clGetDeviceIDs': ['int', ['size_t', 'ulong', 'uint', sizeTPtr, uintPtr]], 'clCreateContext': ['pointer', ['pointer', 'uint', sizeTPtr, 'pointer', 'pointer', intPtr]], 'clReleaseContext': ['int', ['pointer']], 'clCreateProgramWithSource': ['pointer', ['pointer', 'uint', StringArray, sizeTPtr, intPtr]], 'clBuildProgram': ['int', ['pointer', 'uint', sizeTPtr, 'string', 'pointer', 'pointer']], 'clReleaseProgram': ['int', ['pointer']], 'clCreateKernel': ['pointer', ['pointer', 'string', intPtr]], 'clReleaseKernel': ['int', ['pointer']], 'clCreateBuffer': ['pointer', ['pointer', 'ulong', 'size_t', 'pointer', intPtr]], 'clReleaseMemObject': ['int', ['pointer']], 'clSetKernelArg': ['int', ['pointer', 'uint', 'size_t', 'pointer']], 'clCreateCommandQueue': ['pointer', ['pointer', 'size_t', 'ulong', intPtr]], 'clReleaseCommandQueue': ['int', ['pointer']], 'clEnqueueReadBuffer': ['int', ['pointer', 'pointer', 'bool', 'size_t', 'size_t', 'pointer', 'uint', 'pointer', 'pointer']], 'clEnqueueNDRangeKernel': ['int', ['pointer', 'pointer', 'uint', sizeTPtr, sizeTPtr, sizeTPtr, 'uint', 'pointer', 'pointer']] }); // エラーチェック const checkError = (err, title = '') => { if (err instanceof Buffer) { // ポインタの場合はエラーコードを取り出す err = intPtr.get(err); } if (err != 0) { throw new Error(`${title} Error: ${err}`); } }; // 演算対象データ const data = [1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9]; const functionName = process.argv[2] // OpenCL コードの読み込み(ファイルから) const code = fs.readFileSync(process.argv[3]); const releaseList = []; try { ・・・ OpenCL API の呼び出し処理 ・・・ } finally { // リソースの解放 releaseList.reverse().forEach( f => f() ); }
clCreateProgramWithSource
へ OpenCL のコードを渡す際に、ref-array
で作成した String の配列 StringArray
を使っています。
OpenCL 処理部分
番号 | 概要 | OpenCL 関数名 |
---|---|---|
(1) | プラットフォーム取得 | clGetPlatformIDs |
(2) | デバイス取得 | clGetDeviceIDs |
(3) | コンテキスト作成 | clCreateContext |
(4) | コマンドキュー作成 | clCreateCommandQueue |
(5) | プログラム作成 | clCreateProgramWithSource |
(6) | プログラムのビルド | clBuildProgram |
(7) | カーネル作成 | clCreateKernel |
(8) | 引数用のバッファ作成 | clCreateBuffer |
(9) | 引数の設定 | clSetKernelArg |
(10) | 処理の実行 | clEnqueueNDRangeKernel |
(11) | 結果の取得 | clEnqueueReadBuffer |
OpenCL のコードを実行するには (6) のように API を使ってビルドする必要があります。
Node.js と OpenCL 間で配列データ等をやりとりするには (8) で作ったバッファを使います。(入力値をバッファへ書き込んで、出力値をバッファから読み出す)
また、今回は clEnqueueNDRangeKernel
を使って実行しましたが、clEnqueueTask
を使って実行する方法もあります。
calc.js (OpenCL 処理部分)
・・・ try { const platformIdsPtr = ref.alloc(sizeTPtr); // (1) プラットフォーム取得 let res = openCl.clGetPlatformIDs(1, platformIdsPtr, null); checkError(res, 'clGetPlatformIDs'); const platformId = sizeTPtr.get(platformIdsPtr); const deviceIdsPtr = ref.alloc(sizeTPtr); // (2) デバイス取得 (デフォルトをとりあえず使用) res = openCl.clGetDeviceIDs(platformId, CL_DEVICE_TYPE_DEFAULT, 1, deviceIdsPtr, null); checkError(res, 'clGetDeviceIDs'); const deviceId = sizeTPtr.get(deviceIdsPtr); const errPtr = ref.alloc(intPtr); // (3) コンテキスト作成 const ctx = openCl.clCreateContext(null, 1, deviceIdsPtr, null, null, errPtr); checkError(errPtr, 'clCreateContext'); releaseList.push( () => openCl.clReleaseContext(ctx) ); // (4) コマンドキュー作成 const queue = openCl.clCreateCommandQueue(ctx, deviceId, 0, errPtr); checkError(errPtr, 'clCreateCommandQueue'); releaseList.push( () => openCl.clReleaseCommandQueue(queue) ); const codeArray = new StringArray([code.toString()]); // (5) プログラム作成 const program = openCl.clCreateProgramWithSource(ctx, 1, codeArray, null, errPtr); checkError(errPtr, 'clCreateProgramWithSource'); releaseList.push( () => openCl.clReleaseProgram(program) ); // (6) プログラムのビルド res = openCl.clBuildProgram(program, 1, deviceIdsPtr, null, null, null) checkError(res, 'clBuildProgram'); // (7) カーネル作成 const kernel = openCl.clCreateKernel(program, functionName, errPtr); checkError(errPtr, 'clCreateKernel'); releaseList.push( () => openCl.clReleaseKernel(kernel) ); const FixedFloatArray = ArrayType('float', data.length); // 入力データ const inputData = new FixedFloatArray(data); const bufSize = inputData.buffer.length; // (8) 引数用のバッファ作成(入力用)し inputData の内容を書き込む const inClBuf = openCl.clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, bufSize, inputData.buffer, errPtr); checkError(errPtr, 'clCreateBuffer In'); releaseList.push( () => openCl.clReleaseMemObject(inClBuf) ); // (8) 引数用のバッファ作成(出力用) const outClBuf = openCl.clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, bufSize, null, errPtr); checkError(errPtr, 'clCreateBuffer Out'); releaseList.push( () => openCl.clReleaseMemObject(outClBuf) ); const inClBufRef = inClBuf.ref(); // (9) 引数の設定 res = openCl.clSetKernelArg(kernel, 0, inClBufRef.length, inClBufRef); checkError(res, 'clSetKernelArg 0'); const outClBufRef = outClBuf.ref(); // (9) 引数の設定 res = openCl.clSetKernelArg(kernel, 1, outClBufRef.length, outClBufRef); checkError(res, 'clSetKernelArg 1'); const ct = ref.alloc(ref.types.uint32, data.length); // (9) 引数の設定 res = openCl.clSetKernelArg(kernel, 2, ct.length, ct); checkError(res, 'clSetKernelArg 2'); const globalPtr = ref.alloc(sizeTPtr); sizeTPtr.set(globalPtr, 0, data.length); // (10) 処理の実行 res = openCl.clEnqueueNDRangeKernel(queue, kernel, 1, null, globalPtr, null, 0, null, null); checkError(res, 'clEnqueueNDRangeKernel'); const resData = new FixedFloatArray(); // (11) 結果の取得 (outClBuf の内容を resData へ) res = openCl.clEnqueueReadBuffer(queue, outClBuf, true, 0, resData.buffer.length, resData.buffer, 0, null, null); checkError(res, 'clEnqueueReadBuffer'); // 結果出力 for (let i = 0; i < resData.length; i++) { console.log(resData[i]); } } finally { // リソースの解放 releaseList.reverse().forEach( f => f() ); }
動作確認
今回は以下の Node.js を使って Windows と Linux の両方で動作確認します。
- Node.js v6.3.1
(a) Windows で実行
「node-ffi で OpenCL を使う」 で構築した環境へ ref-array
をインストールして実行しました。
実行結果 (Windows)
> node calc.js cube cube.cl 1.3310000896453857 10.648000717163086 35.93699645996094 85.18400573730469 166.375 287.4959716796875 456.532958984375 681.4720458984375 970.2988891601562
(b) Linux で実行
前回 の Docker イメージを使って実行します。
calc.js と cube.cl を /vagrant/work へ配置し、Docker コンテナからは /work でアクセスできるようにマッピングしました。
Docker コンテナ実行
$ docker run --rm -it -v /vagrant/work:/work sample/opencl:0.1 bash
Node.js と必要なモジュールのインストール
# curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.2/install.sh | bash ・・・ # source ~/.bashrc # nvm install v6.3.1 ・・・ # npm install -g node-gyp ・・・ # cd /work # npm install ffi ref-array ・・・
/work 内で実行します。
実行結果 (Linux)
# node calc.js cube cube.cl 1.3310000896453857 10.648000717163086 35.93699645996094 85.18400573730469 166.375 287.4959716796875 456.532958984375 681.4720458984375 970.2988891601562