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) が存在しないので、自分で用意する必要がある。具体的には、以下からダウンロードする:
- Khronos OpenCL Registry
- cl.hpp は https://www.khronos.org/registry/OpenCL/api/2.1/cl.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言語では文字列の扱いが煩雑で、文字列を取得するのに
- 長さを取得するために OpenCL の API を呼ぶ
- メモリを確保する
- 実際の文字列を取得するために 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 を使って計算させることについては、次回以降の記事に書く。
この記事の続きはありますでしょうか。