node-ffi で OpenCL を使う
Windows 環境で node-ffi (Node.js Foreign Function Interface) を使って OpenCL の API を呼び出してみました。
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20160627/
なお、OpenCL 上での演算は今回扱いませんが、単純な演算のサンプルは ここ に置いてます。
はじめに
node-ffi のインストール
まずは、node-gyp をインストールしておきます。 node-gyp を Windows 環境で使うには VC++ や Python 2.7 が必要です。
node-gyp インストール例
> npm install -g node-gyp
node-ffi をインストールします。(モジュール名は node-ffi ではなく ffi
です)
node-ffi インストール例
> npm install ffi
node-ffi の使い方
node-ffi では Library
関数を使ってネイティブライブラリの関数をマッピングします。
ffi.Library(<ライブラリ名>, { <関数名>: [<戻り値の型>, [<第1引数の型>, <第2引数の型>, ・・・]], ・・・ })
引数の型などはライブラリのヘッダーファイルなどを参考にして設定します。
例えば、OpenCL.dll (Windows 環境の場合) の clGetPlatformIDs 関数を Node.js から openCl.clGetPlatformIDs(・・・)
で呼び出すには以下のようにします。
Library の使用例
const openCl = ffi.Library('OpenCL', { 'clGetPlatformIDs': ['int', ['uint', sizeTPtr, uintPtr]], ・・・ });
ref モジュールの refType
でポインタ用の型を定義する事が可能です。
refType の使用例
const uintPtr = ref.refType(ref.types.uint32); const sizeTPtr = ref.refType('size_t');
OpenCL の利用
それでは、下記 OpenCL ランタイムをインストールした Windows 環境で、OpenCL の API を 3つほど呼び出してみます。
1. OpenCL のデバイスID取得
まずは、以下を実施してみます。
- (1)
clGetPlatformIDs
を使ってプラットフォームIDを取得 - (2)
clGetDeviceIDs
を使ってデバイスIDを取得
OpenCL (v1.2) のヘッダーファイルを見てみると、プラットフォームIDの型 cl_platform_id
やデバイスIDの型 cl_device_id
はこれ自体がポインタのようなので ※、これらに該当する型は size_t
としました。
※ そのため、プラットフォームID や デバイスID という表現は 適切ではないかもしれません
node-ffi ではポインタを扱うために Buffer
を使います。
そのための補助関数が ref モジュールに用意されており、下記サンプルでは以下を使っています。
- ref モジュールの
alloc
を使って指定した型に応じた Buffer を作成 - 定義した型の
get
を使って Buffer から値を取得
get
を使えば、型のサイズやエンディアンに応じた値を Buffer から取り出してくれます。 (例えば、int32 なら Buffer の readInt32LE や readInt32BE を使って値を取得する)
なお、エラーの有無は clGetPlatformIDs・clGetDeviceIDs の戻り値が 0 かどうかで判定します。(0: 成功、0以外: エラー)
get_device_id.js
'use strict'; const ffi = require('ffi'); const ref = require('ref'); // 定数の定義 const CL_DEVICE_TYPE_DEFAULT = 1; // ポインタ用の型定義 const uintPtr = ref.refType(ref.types.uint32); const sizeTPtr = ref.refType('size_t'); // OpenCL の関数定義 const openCl = ffi.Library('OpenCL', { 'clGetPlatformIDs': ['int', ['uint', sizeTPtr, uintPtr]], 'clGetDeviceIDs': ['int', ['size_t', 'ulong', 'uint', sizeTPtr, uintPtr]] }); // エラーチェック処理 const checkError = (errCode, title = '') => { if (errCode != 0) { throw new Error(`${title} Error: ${errCode}`); } }; const platformIdsPtr = ref.alloc(sizeTPtr); // (1) プラットフォームIDを(1つ)取得 let res = openCl.clGetPlatformIDs(1, platformIdsPtr, null); checkError(res, 'clGetPlatformIDs'); // プラットフォームID(get を使って platformIdsPtr の先頭の値を取得) const platformId = sizeTPtr.get(platformIdsPtr); console.log(`platformId: ${platformId}`); const deviceIdsPtr = ref.alloc(sizeTPtr); // (2) デバイスIDを(1つ)取得 res = openCl.clGetDeviceIDs(platformId, CL_DEVICE_TYPE_DEFAULT, 1, deviceIdsPtr, null); checkError(res, 'clGetDeviceIDs'); // デバイスID(get を使って deviceIdsPtr の先頭の値を取得) const deviceId = sizeTPtr.get(deviceIdsPtr); console.log(`deviceId: ${deviceId}`);
実行結果
> node get_device_id.js platformId: 47812336 deviceId: 4404320
2. OpenCL のプラットフォーム情報取得
次は OpenCL のプラットフォーム情報を取得してみます。
プラットフォーム情報は clGetPlatformInfo
を使って取得します。
- (1)
clGetPlatformInfo
でデータサイズを取得 - (2) バッファを確保
- (3)
clGetPlatformInfo
でデータを取得
platform_info.js
'use strict'; const ffi = require('ffi'); const ref = require('ref'); // 定数の定義 const CL_PLATFORM_PROFILE = 0x0900; const CL_PLATFORM_VERSION = 0x0901; const CL_PLATFORM_NAME = 0x0902; const CL_PLATFORM_VENDOR = 0x0903; const CL_PLATFORM_EXTENSIONS = 0x0904; const CL_PLATFORM_HOST_TIMER_RESOLUTION = 0x0905; const uintPtr = ref.refType(ref.types.uint32); const sizeTPtr = ref.refType('size_t'); const openCl = ffi.Library('OpenCL', { 'clGetPlatformIDs': ['int', ['uint', sizeTPtr, uintPtr]], 'clGetPlatformInfo': ['int', ['size_t', 'uint', 'size_t', 'pointer', sizeTPtr]] }); const checkError = (errCode, title = '') => { if (errCode != 0) { throw new Error(`${title} Error: ${errCode}`); } }; // プラットフォーム情報の出力 const printPlatformInfo = (pid, paramName) => { const sPtr = ref.alloc(sizeTPtr); // (1) データサイズを取得 let res = openCl.clGetPlatformInfo(pid, paramName, 0, null, sPtr); checkError(res, 'clGetPlatformInfo size'); // データサイズの値を取り出す const size = sizeTPtr.get(sPtr); // (2) バッファを確保 const buf = Buffer.alloc(size); // (3) データを取得 res = openCl.clGetPlatformInfo(pid, paramName, size, buf, null); checkError(res, 'clGetPlatformInfo data'); // 出力 console.log(buf.toString()); }; const platformIdsPtr = ref.alloc(sizeTPtr); const res = openCl.clGetPlatformIDs(1, platformIdsPtr, null); checkError(res, 'clGetPlatformIDs'); const platformId = sizeTPtr.get(platformIdsPtr); [ CL_PLATFORM_PROFILE, CL_PLATFORM_VERSION, CL_PLATFORM_NAME ].forEach( p => printPlatformInfo(platformId, p) );
実行結果
> node platform_info.js FULL_PROFILE OpenCL 1.2 Intel(R) OpenCL