[ C++で開発 ]

OpenCLとC++

主にGraphics Processing Unit(GPU)を演算処理に用い、GPUの持つ大量の演算器*1を活用して並列実行することで性能を向上させるフレームワークの1つがOpenCLです。

*1) 2010年代のATI RADEON5800系では、32bit浮動小数点演算器を1600個保有しています。

OpenCL概要

OpenCLは、演算処理プロセッサで実行するコードを記述するOpenCL C言語(C言語の並列拡張)と、アプリケーションプログラムから演算処理を依頼し結果を受け取る制御用のOpenCLランタイムAPIから構成されます。

OpenCL開発環境

GPUを使用するOpenCL開発環境

使用するGPUの製造元が提供する開発キットを入手し、C/C++コンパイラと組み合わせて構築します。

OpenCLプログラミング

OpenCLは共通仕様ですが、ここでは、Windows 7 (64bit版)上で、GPUはRADEON HD5770、開発キットにATI stream SDK v.2.2を使っています。

最初のOpenCLプログラミング

hello.cpp

最初のサンプルコードは、CL/cl.hppにコメントで記載していたものです。

コード全体

コード全体は以下です。

#define __CL_ENABLE_EXCEPTIONS
#include <CL/cl.hpp>
#include <iostream>

const char* helloStr = "__kernel void hello(void) { }";

int main() {
    cl_int err = CL_SUCCESS;
    try {
        std::vector<cl::Platform> platforms;
        cl::Platform::get(&platforms);
        if (platforms.size() == 0) {
            std::cerr << "Platform size 0" << std::endl;
            return -1;
        }

        cl_context_properties properties[] = {
            CL_CONTEXT_PLATFORM, (cl_context_properties)(platforms[0])(), 0};
        cl::Context context(CL_DEVICE_TYPE_GPU, properties);

        std::vector<cl::Device> devices = context.getInfo<CL_CONTEXT_DEVICES>();

        cl::Program::Sources source(1, std::make_pair(helloStr, strlen(helloStr)));
        cl::Program program = cl::Program(context, source);
        program.build(devices);

        cl::Kernel kernel(program, "hello", &err);

        cl::Event event;
        cl::CommandQueue queue(context, devices[0], 0, &err);
        queue.enqueueNDRangeKernel(
            kernel, cl::NullRange, cl::NDRange(4, 4), cl::NullRange, NULL, &event
            );
        event.wait();
    } catch (cl::Error err) {
        std::cerr << "ERROR: " << err.what() << "(" << err.err() << ")" << std::endl;
    }
    return EXIT_SUCCESS;
}
ヘッダーのインクルード
#define __CL_ENABLE_EXCEPTIONS
#include <CL/cl.hpp>

OpenCLライブラリで例外を有効にするため、1行目のデファイン文を記述します。
OpenCLのC++ APIを使用するため、2行目のインクルード文を記述します。
なお、この記述はWindows/Linuxの場合で、MacOSの場合は<OpenCL/cl.hpp>となります。

OpenCL C言語ソースの記述
const char* helloStr = "__kernel void hello(void) { }";

演算用プロセッサで実行するコードをOpenCL C言語のソースで記述しchar* で参照します。
ここではインラインで記述していますが、通常は別ファイルに記述しておいて、ファイルを読み込んでchar*で参照するでしょう。

プラットフォームの特定
std::vector<cl::Platform> platforms;
cl::Platform::get(&platforms);

OpenCLが実行できるプラットフォームの一覧を取得します。

コンテクストの作成
cl_context_properties properties[] = {
        CL_CONTEXT_PLATFORM, (cl_context_properties)(platforms[0])(), 0};
cl::Context context(CL_DEVICE_TYPE_GPU, properties);

先に特定したプラットフォーム上での実行環境OpenCLコンテクストを作成します。コンテクストには、使用したいデバイス種類を指定します。

デバイスの取得
std::vector<cl::Device> devices = context.getInfo<CL_CONTEXT_DEVICES>();

先に作成したコンテクストからデバイスを取得します。

プログラムの作成
cl::Program::Sources source(1, std::make_pair(helloStr, strlen(helloStr)));
cl::Program program = cl::Program(context, source);
program.build(devices);

OpenCLデバイスで実行するプログラムを作成します。プログラム(ソースコード)を文字列(char*)で参照し、実行するコンテキストと合わせてプログラムオブジェクトを生成します。そして、コンパイルします。

カーネルの作成
cl::Kernel kernel(program, "hello", &err);

1つのカーネルは、1つの関数に対応します。第2引数はカーネル関数名を指定します。

コマンドキューの作成
cl::CommandQueue queue(context, devices[0], 0, &err);

アプリケーション側からデバイスに制御コマンドを送るために使用するコマンドキューを作成します。

カーネルの実行
queue.enqueueNDRangeKernel(
    kernel, cl::NullRange, cl::NDRange(4, 4), cl::NullRange, NULL, &event
);

カーネル関数をコマンドキューに投入します。

参考文献

仕様・リファレンス


This page is written by Toru TAKAHASHI.(torutk atmark 02.246.ne.jp)