C++ から OpenCL を使う(その0):準備


21世紀を生きる者として、並列コンピューティング技術の一つや二つは扱えなければならない。

この間書いた Haskell で並列評価する記事は CPU のマルチコアを活用するものだったが、今度は CPU 以外のデバイス(GPU を念頭に置く)を活用できる技術を使いたい。いわゆる GPGPU をやりたい。(技術に入門するのが目的であって、計算能力を活かして特にこれがしたいという訳ではない。そもそも筆者の手持ちの GPU の計算能力などたかが知れているが……)

N 社の GPU を前提にするなら CUDA という選択肢もあるだろうが、ここでは移植性を重視して OpenCL を試すことにする。(というか、 Mac ユーザー的には CUDA という選択肢は存在しないも同然)

OpenCL にはバージョン 1 系 (1.x) とバージョン 2 系 (2.x) があるが、ここではバージョン 1 系を使う。

C++ bindings にも、 1.x のみ対応の cl.hpp と、 2.x にも対応する cl2.hpp があるが、ここでは前者を使うことにする。

準備:Mac OS X の場合

OpenCL のヘッダは OpenCL.framework としてシステムにデフォルトで入っている。

しかし、 C++ bindings (cl.hpp, cl2.hpp) が存在しないので、自分で用意する必要がある。具体的には、以下からダウンロードする:

ちなみに、 cl.hpp を生成するスクリプトは、ここのリポジトリに置かれている:

大雑把に言って次の手順でビルドできた(要 CMake と doxygen):

$ git clone https://github.com/KhronosGroup/OpenCL-CLHPP.git
$ cd OpenCL-CLHPP/
$ mkdir build
$ cd build
$ cmake -DBUILD_EXAMPLES=0 -DBUILD_TESTS=0 ..
$ make
$ ls include/CL/
cl.hpp  cl2.hpp
$ cd docs
$ doxygen

OpenCL にヘッダファイルの名前だが、他のプラットフォームでは <CL/cl.h> となるところが、 Mac の場合は <OpenCL/cl.h> となる。なぜ OpenGL の轍を踏んだのか…。(C++ bindings を自分で用意する場合はディレクトリ名を OpenCL/ にせず、 CL/cl.hpp として使えるようにするのもアリだと思う)

なお、 Xcode で開発する際は GCD との統合だのラッパーの自動生成だのをやってくれるようだが、この記事ではそういうのは使わず、あくまでクロスプラットフォームな方法でやる。

準備:Windows の場合

GPU の提供元が用意している SDK を使う。どの SDK を使っても実行時に利用できる OpenCL 実装の一覧は同じである(…のはず)。

Intel SDK の場合

ここから入手できる:

インストール後、環境変数 INTELOCLSDKROOT が設定される。

ヘッダファイルは %INTELOCLSDKROOT%/include/CL/*, ライブラリファイルは %INTELOCLSDKROOT%/lib/{x86, x64}/OpenCL.lib として存在する。

NVIDIA CUDA の場合

CUDA Toolkit を入れる:

インストール後、環境変数 CUDA_PATH が設定される。

ヘッダファイルは %CUDA_PATH%/include, ライブラリファイルは %CUDA_PATH%/lib/{Win32, x64} 以下に存在する。

AMD の場合

自分で調べてください……。

準備:Linux の場合

自分で調べてください……。

  • GPGPU – ArchWiki (Linux における大抵のトピックについて ArchWiki は有益である)

Debian の場合は ocl-icd-opencl-dev と pocl-opencl-icd を入れればテストプログラムが動作した(後者は具体的な OpenCL の実装を提供する。コンパイルを通すだけなら後者はなくても大丈夫だが、 OpenCL の実装が入っていないと clGetPlatformIDs がエラーコード -1001 を返す)。

プラットフォーム及びデバイスの列挙

まず、 OpenCL のデバイスとしてどのようなものが使えるか調べてみよう。

C の API で言えば clGetPlatformIDs, clGetPlatformInfo, clGetDeviceIDs, clGetDeviceInfo あたりを使う。ただ、C言語では文字列の扱いが煩雑で、文字列を取得するのに

  1. 長さを取得するために OpenCL の API を呼ぶ
  2. メモリを確保する
  3. 実際の文字列を取得するために OpenCL の API をもう一度呼ぶ

という手順を踏む必要がある。C++ Binding を使えば一発で std::string を取得できるので楽チンである。

この他の C++ を使うメリットとしては、(マクロ __CL_ENABLE_EXCEPTIONS を事前に定義しておけば)エラー時に例外が飛ぶようになる、というものがある。C言語だと例外がないのでいちいちエラーチェックする必要がある。

実際のプログラムはこれ:

実行例1:MacBook Pro (Late 2013)

スペック:

  • CPU: Intel Core i5-4288U 2.6GHz
  • 内蔵 GPU: Intel Iris Graphics 5100

コンパイルの際に、自分で用意した CL/cl.hpp のパスを指定する。また、 -framework OpenCL を指定する。

$ clang++ -std=c++11 -IOpenCL-CLHPP/build/include platforms.cpp -framework OpenCL
$ ./a.out
Platform #0:
  Profile: FULL_PROFILE
  Name: Apple
  Vendor: Apple
  Extensions: cl_APPLE_SetMemObjectDestructor cl_APPLE_ContextLoggingFunctions cl_APPLE_clut cl_APPLE_query_kernel_names cl_APPLE_gl_sharing cl_khr_gl_event
  Device #0 (CPU):
    Name: Intel(R) Core(TM) i5-4288U CPU @ 2.60GHz
    Vendor: Intel
    Device Version: OpenCL 1.2 
    Driver Version: 1.1
    Extensions: cl_APPLE_SetMemObjectDestructor cl_APPLE_ContextLoggingFunctions cl_APPLE_clut cl_APPLE_query_kernel_names cl_APPLE_gl_sharing cl_khr_gl_event cl_khr_fp64 cl_khr_global_int32_base_atomics cl_khr_global_int32_extended_atomics cl_khr_local_int32_base_atomics cl_khr_local_int32_extended_atomics cl_khr_byte_addressable_store cl_khr_int64_base_atomics cl_khr_int64_extended_atomics cl_khr_3d_image_writes cl_khr_image2d_from_buffer cl_APPLE_fp64_basic_ops cl_APPLE_fixed_alpha_channel_orders cl_APPLE_biased_fixed_point_image_formats cl_APPLE_command_queue_priority
    Max Compute Units: 4
    Preferred Vector Width (Float): 4
    Preferred Vector Width (Double): 2
  Device #1 (GPU):
    Name: Iris
    Vendor: Intel
    Device Version: OpenCL 1.2 
    Driver Version: 1.2(Apr 22 2017 16:00:44)
    Extensions: cl_APPLE_SetMemObjectDestructor cl_APPLE_ContextLoggingFunctions cl_APPLE_clut cl_APPLE_query_kernel_names cl_APPLE_gl_sharing cl_khr_gl_event cl_khr_global_int32_base_atomics cl_khr_global_int32_extended_atomics cl_khr_local_int32_base_atomics cl_khr_local_int32_extended_atomics cl_khr_byte_addressable_store cl_khr_image2d_from_buffer cl_khr_gl_depth_images cl_khr_depth_images cl_khr_3d_image_writes 
    Max Compute Units: 40
    Preferred Vector Width (Float): 1
    Preferred Vector Width (Double): 0

Apple 様による実装で、 CPU と GPU がそれぞれ1個ずつ見えている。GPU は内蔵のやつなので、ヘボい(倍精度に非対応だったり…)。

実行例2:Windows on 自作PC

スペック:

  • CPU: Intel Core i7-3770 3.4GHz
  • 内蔵 GPU: Intel HD Graphics 4000
  • GPU: NVIDIA GeForce GTX 1050

CPU の世代と GPU の世代が違うのは、グラフィックボードを後から追加したためである。グラボを追加した際は4Kディスプレイに60Hzで出力できるやつ、と思って選んだので、 VR や本格的な GPGPU はできない。

UEFI の設定をいじって、外部 GPU の存在下でも内蔵 GPU が無効にならないようにしている。

以下にコンパイル例をいくつか示す。いずれも、64ビット版の開発ツールを仮定している。

コンパイル例1(Visual C++ with Intel SDK):

cl /nologo /EHsc /I"%INTELOCLSDKROOT%\include" platforms.cpp /link /LIBPATH:"%INTELOCLSDKROOT%\lib\x64" OpenCL.lib

コンパイル例2(Visual C++ with CUDA):

cl /nologo /EHsc /I"%CUDA_PATH%\include" platforms.cpp /link /LIBPATH:"%CUDA_PATH%\lib\x64" OpenCL.lib

コンパイル例3(MinGW-w64/MSYS2 with Intel SDK):

g++ -oplatforms.exe -I"${INTELOCLSDKROOT}/include" platforms.cpp -L"${INTELOCLSDKROOT}/lib/x64" -lOpenCL

g++ --version したところ、バージョンは 6.3.0 と出てきた。このバージョンでは C++11 の機能がデフォルトで使えるようだ。

コンパイル例4(MinGW-w64/MSYS2 with CUDA):

g++ -oplatforms.exe -I"${CUDA_PATH}/include" platforms.cpp -L"${CUDA_PATH}/lib/x64" -lOpenCL

実行結果はいずれも同じで、以下のようになった。

Platform #0:
  Profile: FULL_PROFILE
  Name: NVIDIA CUDA
  Vendor: NVIDIA Corporation
  Extensions: cl_khr_global_int32_base_atomics cl_khr_global_int32_extended_atomics cl_khr_local_int32_base_atomics cl_khr_local_int32_extended_atomics cl_khr_fp64 cl_khr_byte_addressable_store cl_khr_icd cl_khr_gl_sharing cl_nv_compiler_options cl_nv_device_attribute_query cl_nv_pragma_unroll cl_nv_d3d10_sharing cl_khr_d3d10_sharing cl_nv_d3d11_sharing cl_nv_copy_opts cl_nv_create_buffer
  Device #0 (GPU):
    Name: GeForce GTX 1050
    Vendor: NVIDIA Corporation
    Device Version: OpenCL 1.2 CUDA
    Driver Version: 382.05
    Extensions: cl_khr_global_int32_base_atomics cl_khr_global_int32_extended_atomics cl_khr_local_int32_base_atomics cl_khr_local_int32_extended_atomics cl_khr_fp64 cl_khr_byte_addressable_store cl_khr_icd cl_khr_gl_sharing cl_nv_compiler_options cl_nv_device_attribute_query cl_nv_pragma_unroll cl_nv_d3d10_sharing cl_khr_d3d10_sharing cl_nv_d3d11_sharing cl_nv_copy_opts cl_nv_create_buffer
    Max Compute Units: 5
    Preferred Vector Width (Float): 1
    Preferred Vector Width (Double): 1
Platform #1:
  Profile: FULL_PROFILE
  Name: Intel(R) OpenCL
  Vendor: Intel(R) Corporation
  Extensions: cl_intel_dx9_media_sharing cl_khr_byte_addressable_store cl_khr_d3d11_sharing cl_khr_dx9_media_sharing cl_khr_gl_sharing cl_khr_global_int32_base_atomics cl_khr_global_int32_extended_atomics cl_khr_icd cl_khr_local_int32_base_atomics cl_khr_local_int32_extended_atomics
  Device #0 (CPU):
    Name: Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz
    Vendor: Intel(R) Corporation
    Device Version: OpenCL 1.2 (Build 76427)
    Driver Version: 3.0.1.10891
    Extensions: cl_khr_fp64 cl_khr_icd cl_khr_global_int32_base_atomics cl_khr_global_int32_extended_atomics cl_khr_local_int32_base_atomics cl_khr_local_int32_extended_atomics cl_khr_byte_addressable_store cl_intel_printf cl_ext_device_fission cl_intel_exec_by_local_thread cl_khr_gl_sharing cl_intel_dx9_media_sharing cl_khr_dx9_media_sharing cl_khr_d3d11_sharing
    Max Compute Units: 8
    Preferred Vector Width (Float): 1
    Preferred Vector Width (Double): 1
  Device #1 (GPU):
    Name: Intel(R) HD Graphics 4000
    Vendor: Intel(R) Corporation
    Device Version: OpenCL 1.2
    Driver Version: 10.18.10.4358
    Extensions: cl_intel_d3d11_nv12_media_sharing cl_intel_dx9_media_sharing cl_khr_3d_image_writes cl_khr_byte_addressable_store cl_khr_d3d10_sharing cl_khr_d3d11_sharing cl_khr_depth_images cl_khr_dx9_media_sharing cl_khr_gl_depth_images cl_khr_gl_event cl_khr_gl_msaa_sharing cl_khr_gl_sharing cl_khr_global_int32_base_atomics cl_khr_global_int32_extended_atomics cl_khr_icd cl_khr_image2d_from_buffer cl_khr_local_int32_base_atomics cl_khr_local_int32_extended_atomics cl_intel_accelerator cl_intel_motion_estimation
    Max Compute Units: 16
    Preferred Vector Width (Float): 1
    Preferred Vector Width (Double): 0
Platform #2:
  Profile: FULL_PROFILE
  Name: Experimental OpenCL 2.1 CPU Only Platform
  Vendor: Intel(R) Corporation
  Extensions: cl_khr_icd cl_khr_global_int32_base_atomics cl_khr_global_int32_extended_atomics cl_khr_local_int32_base_atomics cl_khr_local_int32_extended_atomics cl_khr_byte_addressable_store cl_khr_depth_images cl_khr_3d_image_writes cl_intel_exec_by_local_thread cl_khr_spir cl_khr_dx9_media_sharing cl_intel_dx9_media_sharing cl_khr_d3d11_sharing cl_khr_gl_sharing cl_khr_fp64 cl_khr_image2d_from_buffer
  Device #0 (CPU):
    Name: Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz
    Vendor: Intel(R) Corporation
    Device Version: OpenCL 2.1 (Build 18)
    Driver Version: 6.3.0.1904
    Extensions: cl_khr_icd cl_khr_global_int32_base_atomics cl_khr_global_int32_extended_atomics cl_khr_local_int32_base_atomics cl_khr_local_int32_extended_atomics cl_khr_byte_addressable_store cl_khr_depth_images cl_khr_3d_image_writes cl_intel_exec_by_local_thread cl_khr_spir cl_khr_dx9_media_sharing cl_intel_dx9_media_sharing cl_khr_d3d11_sharing cl_khr_gl_sharing cl_khr_fp64 cl_khr_image2d_from_buffer
    Max Compute Units: 8
    Preferred Vector Width (Float): 1
    Preferred Vector Width (Double): 1

Intel の内蔵 GPU は倍精度に非対応だが、 GeForce GTX 1050 は倍精度に対応している。

実行例3:仮想環境の Debian / POCL

物理マシン自体は実行例2と同じである。仮想環境でのGPUの扱いがよくわかっていないので、 GPU 用のドライバは入れていない。

GCC のバージョンは 6.3.0 だった。

$ g++ platforms.cpp -lOpenCL
$ ./a.out
Platform #0:
  Profile: FULL_PROFILE
  Name: Portable Computing Language
  Vendor: The pocl project
  Extensions: cl_khr_icd
  Device #0 (unknown):
    Name: pthread-Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz
    Vendor: GenuineIntel
    Device Version: OpenCL 2.0 pocl
    Driver Version: 0.13
    Extensions: cl_khr_byte_addressable_store cl_khr_global_int32_base_atomics cl_khr_global_int32_extended_atomics cl_khr_local_int32_base_atomics cl_khr_local_int32_extended_atomics cl_khr_spir cl_khr_int64 cl_khr_fp64 cl_khr_int64_base_atomics cl_khr_int64_extended_atomics
    Max Compute Units: 8
    Preferred Vector Width (Float): 4
    Preferred Vector Width (Double): 2

実際に OpenCL を使って計算させることについては、次回以降の記事に書く。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です