D言語と Policy に基づく設計
https://github.com/ShigekiKarita/d-nvrtc
d-nvrtc (dnv) という CUDA のD言語用ラッパーを作りました。この規模のライブラリを D 言語で書いたのは初めてで、面白いノウハウが溜まったので久々に記事を書いています。 d-nvrtc の売り (目標) は D 言語の持つ強い静的型付けと GC の手軽さで簡単に高速な CUDA プログラミングができることです。現在はこんな感じで動作します。
import dnv;
import std.stdio;
import std.random;
import std.range;
int n = 10;
auto gen = () => new Array!float(generate!(() => uniform(-1f, 1f)).take(n).array());
auto a = gen();
auto b = gen();
auto c = new Array!float(n); // CUDA メモリも GC で管理
enum code = Code(
"saxpy", q{float *A, float *B, float *C, int numElements},
q{
int i = blockDim.x * blockIdx.x + threadIdx.x;
if (i < numElements) C[i] = A[i] + B[i];
});
auto saxpy = new TypedKernel!(code);
saxpy(a, b, c, n); // 引数が型が違うとコンパイルエラー
foreach (ai, bi, ci; zip(a.to_cpu(), b.to_cpu(), c.to_cpu())) {
assert(ai + bi == ci);
}
属性を持つクラスの冗長性
CUDAカーネルクラス Kernel
には 2 つの属性があります。1 つは NVRTC をラップしたクラス Compiler
の型検査による属性 (静的・動的・無し) です。もう 1 つはCUDAカーネルを呼び出すクラス Launcher
による属性 (e.g., 要素数によるもの・動的共有メモリを使うもの・ユーザ定義) といった具合です。
ここで問題となるのは、例に上げただけでも $3 \times 3 = 9$ 通りのパターンを実装する必要があります。継承なりで重複をさけることはできるものの、 冗長であり命名の面倒さが発生します。
class StaticCompiler : Compiler { ... }
class DynamicSharedLauncher : Launcher { ... }
// ひどい名前のクラス
class StaticDynamicSharedKernel : Kernel {
Compiler compiler = new StaticCompiler;
Launcher launcher = new SharedLauncher;
}
// 何通りもの似たようなクラス
...
Policy とは
Policy による冗長性の解決
Policy 間のブラックリスト
D言語における依存性注入
Scala などでは依存性注入 (DI) というパターンがでてくる。 これは D 言語の interface とよくにた trait (D言語のそれとは違う) の多重継承によって 柔軟な設計と疎結合性を実現している。 先に述べたブラックリスト的な機能を実現する方法は