9.4. バッファオブジェクトのマップ

バッファオブジェクトのマップは、ホストメモリとデバイス(カーネルインスタンスからアクセス可能なメモリ)のデータ同期を行ないます。

外部(PCIe等)ボード上のGPUデバイスでマップをするのは負荷が高いため、GeForce/Tesla/Radeon/FuryといったNVIDIAやAMDのデバイスでは通常は行なわない処理です。

反面Intelの内蔵GPUボード、AMDのAPUでは、CPUとGPUがホストメモリを共有するため、バッファをコピーするより負荷を軽減する効果を持ちます。いわゆるゼロコピーなため、効率的な実行処理が可能となります。

9.4.1. clEnqueueMapBuffer

clEnqueueMapBuffer関数はバッファオブジェクトの領域からホストアドレス空間へのマップを行いマップされた領域へのポインタを戻すコマンドを挿入します。マップされた領域は、バッファオブジェクトの領域と、ホストメモリで1対1で対応するコピーを保持します。

注記

詳しくは「表:clEnqueueMapBuffer」(表B.36「表:clEnqueueMapBuffer」)と「表:cl_map_flags」(表B.50「表:cl_map_flags」)を参照ください。

関数の定義は以下のようになります。

ByteBuffer org.jocl.CL.clEnqueueMapBuffer(
    cl_command_queue command_queue, //(1)
    cl_mem buffer, //(2)
    boolean blocking_map, //(3)
    long map_flags, //(4)
    long offset, //(5)
    long cb, //(6)
    int num_events_in_wait_list, //(7)
    cl_event[] event_wait_list, //(8)
    cl_event event, //(9)
    int[] errcode_ret) //(10)

(1)

マップコマンドを挿入するコマンドキューを指定。

(2)

有効なバッファオブジェクトを指定。command_queueとbuffer は同じOpenCLコンテキスト上で作成されている必要がある。

(3)

マップ操作をブロッキングまたはノンブロッキングで行うかを指定。blocking_mapにCL_TRUE を指定する場合、bufferの指定した領域がマップされるまでclEnqueueMapBufferは戻らない。

(4)

マップの設定を行うビットフィールド。

(5)

バッファオブジェクトのマップを行う際に、マップの開始位置をどれだけずらすかをバイトで指定。

(6)

マップする領域のサイズをバイトで指定します。

(7)

event_wait_list で指定したイベントオブジェクトの数を指定します。

(8)

このコマンドが実行される前に完了していなければならないイベントを指定。 event_wait_listがNULLの場合、このコマンドはどのイベントの完了待機もしない。

(9)

このコマンドを識別するイベントオブジェクトが戻され、コマンド完了の確認やコマンド完了待機をする。eventがNULLの場合、アプリケーションはコマンドの状況確認やコマンド完了待ちを行えない。代わりに、clEnqueueBarrierWithWaitListを使うことができる。

(10)

適切なエラーコードを戻す。errcode_retがNULLの場合、エラーコードは戻さない。

注記

OpenCLイメージを解説した項目にcl_map_flags(表B.50「表:cl_map_flags」)を解説した項目があるので詳細はそちらを参照ください。

実装例

clEnqueueMapBufferは内蔵型GPUを使う場合は推奨設定となります。つまりCopyやReadと名前のつく関数よりも効率的なバッファへのアクセスが可能となります。

マップコマンドは同期を行いますが、いつ同期が行なわれるかタイミングの面で保証がありません。そのためカーネルコマンド実行後にマップをするのが一般的な用例となります。

重要

この項目ではカーネルの実行をする例はありませんが、マップコマンドのキューへの挿入は、カーネル実行後に行なうようにしてください。マップはパフォーマンス的にボトルネックとなりえるので、不用意に使用しないでください。

以下がコード例となります。

ByteBuffer output = clEnqueueMapBuffer(queue,
        data_mem,
        CL_TRUE,
        CL_MAP_WRITE,
        0,
        Sizeof.cl_int*SIZE,
        0,
        null,
        null,
        null);

mapする変数(output)は手動でメモリ領域を確保できませんので、clEnqueueMapBufferが自動で領域を割り当てます。

マップ済みのオブジェクトについては、不要になり次第clEnqueueUnmapMemObjectを呼び出してください。

clEnqueueUnmapMemObject(queue, data_mem, output, 0, null, null);

unmapコマンドを実行することにより変更内容がデバイスの持つメモリーに送られます。さらにunmapコマンドは、マップカウントをデクリメントしますので、マップ後にunmapを行なうことで、カウントをゼロに戻すことができます。

それではclEnqueueMapBufferを実装したサンプルソースコードを見てみましょう。

package com.book.jocl.buffer;

import static org.jocl.CL.*;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import org.jocl.CL;
import org.jocl.Pointer;
import org.jocl.Sizeof;
import org.jocl.cl_command_queue;
import org.jocl.cl_context;
import org.jocl.cl_context_properties;
import org.jocl.cl_device_id;
import org.jocl.cl_mem;
import org.jocl.cl_platform_id;
import org.jocl.cl_program;

public class FillMapBufferTest {

        private static final int SIZE = 4;

    private static cl_context context;
    private static cl_command_queue queue;
    private static cl_program program;

        public static void main(String[] args) throws Exception {

                CL.setExceptionsEnabled(true);

                cl_platform_id[] platform = new cl_platform_id[1];
                cl_device_id[] device = new cl_device_id[1];
                int[] num_devices = new int[1];

                clGetPlatformIDs(1, platform, null);
                clGetDeviceIDs(platform[0], CL_DEVICE_TYPE_GPU, 1, device, num_devices);

                cl_context_properties props = new cl_context_properties();
                props.addProperty(CL_CONTEXT_PLATFORM, platform[0]);
                context = clCreateContext(props, 1, device, null, null, null);

                queue = clCreateCommandQueue(context, device[0], 0, null);

                cl_mem data_mem = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_ALLOC_HOST_PTR,
                                Sizeof.cl_int * SIZE, null, null);

                int[] pattern = new int[] {1};
                clEnqueueFillBuffer(queue, data_mem, Pointer.to(pattern), Sizeof.cl_int, 0, Sizeof.cl_int*SIZE, 0, null, null);


                ByteBuffer output = clEnqueueMapBuffer(queue,
                                data_mem,
                                CL_TRUE,
                                CL_MAP_WRITE,
                                0,
                                Sizeof.cl_int*SIZE,
                                0,
                                null,
                                null,
                                null);

                clEnqueueUnmapMemObject(queue, data_mem, output, 0, null, null);
                clFinish(queue);

                output.order(ByteOrder.LITTLE_ENDIAN);

                for(int j = 0; j < SIZE; ++j) {
                        System.out.println(output.getInt());
                }

                clReleaseMemObject(data_mem);
                clReleaseDevice(device[0]);
                clReleaseContext(context);
                clReleaseCommandQueue(queue);
                clReleaseProgram(program);
        }

}

上記のプログラムの出力は以下のようになります。

1
1
1
1

Copyright 2018-2019, by Masaki Komatsu