node-ffi で OpenCL を使う

Windows 環境で node-ffi (Node.js Foreign Function Interface) を使って OpenCLAPI を呼び出してみました。

サンプルソース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 環境で、OpenCLAPI を 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