OpenCLではメモリオブジェクト(抽象クラス)を実装したバッファオブジェクトをカーネル関数に引数として渡して、下層のマイクロプロセッサ(EU等)にSIMD命令を送ります。
このためまずは、バッファオブジェクトの初期化には以下のクラスを使います。
バッファを生成する際にはホスト内のデータを指すポインタを使って処理するか、NULL値を指定して新たなメモリ領域を割り当てる方法があります。
バッファオブジェクトを生成後にホストプログラムでアクセスするには以下の読み込み・書き込み、複製関数を使います。
ちなみに恥ずかしながら本書が必死にプロモーションしているIntelの内蔵GPUボードでは、メモリはホストにあるため、データの複製・ロード・書き込みという作業よりも効率的な方法があります。
そのためIntelでは以下の関数を使うよう推奨しています。
バッファをホストメモリの領域とマップさせることにより余計な処理を避けるためです。
pyopencl.BufferはC言語/C++のメモリやポインタに慣れている読者にとっては、mallocやnewのOpenCLバージョンというと分かりやすいかもしれません。
詳しくは「表:clCreateBuffer」(Table B.29, “表:clCreateBuffer”)を参照ください。
関数の定義は以下のようになります。
class pyopencl.Buffer( context, #(1) flags, #(2) size=0, #(3) hostbuf=None) #(4)
ホストポインタ(hostbuf)引数には、バッファの内容となるデータをいれます。
hostbufについてはNone値をいれる(又は指定しない)ことも可能です。一度生成したバッファーにはデータをコピー、マップ、書き込み、読み込みが可能ですので、生成する際にNULLにして領域を空にしておくことがあります。
詳しくは「表:cl_mem_flags」(Table B.30, “cl_mem_flags”)を参照ください。
メモリーフラグは、Bufferクラスのコンストラクタのflag引数のビットフィールドとして使用されます。
以下がmem_flagsクラスの定義となります。
class pyopencl.mem_flags
「表:cl_mem_flags」(Table B.30, “cl_mem_flags”)に個々の値の解説がありますが、数点補足させて頂きます。
まずアクセス修飾子に相当する3つのフィールドとなりますが、これらはカーネルプログラミングにも該当するフラグがあるためマッチさせる必要があります。
WRITE_ONLYフラグで生成したバッファーは以下のようにカーネルで宣言します。
__kernel void func(__global __write_only float* arg1)
READ_ONLYフラグで生成したバッファーは以下のようにカーネルで宣言します。
__kernel void func(__global __read_only float* arg1)
READ_WRITEフラグで生成したバッファーは以下のようにカーネルで宣言します。
__kernel void func(__global __read_write float* arg1)
Intelの内蔵GPU(iGPU)ボードは、メモリ領域空間をCPUと共有で使います。そのため、4GBのメモリの場合でも、実質3GBしか割り当てられない計算ですが、筆者の環境では、これはより小さく1GBのメモリ領域が確保されています。
Intel-iGPU(例:HD Graphics、Iris Pro)を使う場合にはメモリーをデバイス-ホスト間でコピーしないように注意してください。ホストのメモリ領域(メインメモリ)はデバイスの領域と同じ物理デバイスを使っているため、ホストメモリをそのまま活用するほうがパフォーマンスがあがります。
mem_flagsで解説されている通り、COPY_HOST_PTRは、ホストポインタをコピーするというフラグです。そのためhost_ptrがNULLの状態、つまりホストメモリにポインタが存在しない場合は使うことができません。
import pyopencl as cl import numpy as np input = np.arange(16).astype(np.int32) mf = cl.mem_flags input_mem = cl.Buffer( ctx, mf.COPY_HOST_PTR, #(1) hostbuf=input)
CL_MEM_COPY_HOST_PTRを指定。 |
USE_HOST_PTRは、host_ptr引数で指定したポインタをそのまま使ったバッファオブジェクトを生成します。このオプションは、無駄なデータ転送がないため、パフォーマンスを大幅に向上させます。
import pyopencl as cl import numpy as np input = np.arange(16).astype(np.int32) mf = cl.mem_flags input_mem = cl.Buffer( ctx, mf.USE_HOST_PTR, #(1) hostbuf=input)
USE_HOST_PTRを指定。 |
ALLOC_HOST_PTRは、バッファオブジェクトのためにメモリ領域の確保をします。このため、host_ptrはNULL値でないと使うことはできません。
上記のUSE_HOST_PTRと異なる点は、バッファオブジェクトは初期化時の空の状態という点です。以下のコードでは、出力用変数(カーネルでは引数として渡す)として使っているため、空でも構いません。
この領域に対してホストメモリのデータをマップして、カーネルにたいして処理に必要なデータを提供させることもできます。
import pyopencl as cl import numpy as np input = np.arange(16).astype(np.int32) mf = cl.mem_flags output_mem = cl.Buffer( ctx, mf.ALLOC_HOST_PTR, #(1) size=input.nbytes)
ALLOC_HOST_PTRを指定。 |
Copyright 2018-2019, by Masaki Komatsu