MATLABでCUDAコードを動かそう!

はじめに

こんにちは。rej55です。
この記事はrogy Advent Calender 2017の3日目の記事です。

私はロボット技術研究会の中ではMATLABやSimulinkを用いていろいろなものを作る活動をしています。

過去記事

工大祭展示「Raspberry Pi アンプシミュレータ」
MATLAB FBX LoaderとMATLAB 3D Model Rendererの開発
MATLABでGPUコンピューティング

今回の記事について

今回の記事のテーマは「MATLABでCUDAコードを動かそう!」というものです。
内容としては過去記事の「MATLABでGPUコンピューティング」に近いですが、今回の内容はMATLABのGPUコンピューティング機能を直接利用するのではなく、自作のCUDAのプログラムをMATLAB上で利用しようというものになります。

MEXについて

MATLABは、C/C++やFortranといった言語で構築された関数・サブルーチンを呼び出すことができます。
C/C++およびFortranコードをMATLABから呼び出すことができるようにビルドしたファイルをMEXファイルと呼びます。
今回のテーマであるCUDAコードにおいてもこのMEX機能を利用して実現します。

まず、C/C++をベースとしたMEXの利用法について簡単に説明します。
MEXによってC/C++の関数を利用する場合、main関数に代わって次のようなゲートウェイ関数を定義します。

MATLABからMEXを呼び出すと、まず上記のmexFunctionが呼び出されます。
mexFunction関数の引数のうちnlhs, nrhsは実際にMATLAB上で実行されたMEX関数の戻り値の個数、引数の個数に対応しています。
*plhs[], *prhs[]は各戻り値および引数が確保されているメモリの先頭アドレスとなります。

MEXにおけるメモリ管理

MEXを実際に使う上で一番難しいのは、メモリ管理だと思っています。
何が難しいのかというと、そのメモリをMATLABが所有権を持つのか、MEXが所有権を持つのかという点がわかりにくいところだと思います。

基本的には、MEXの中で動的に確保したメモリはMEXの中で解放すべきですが、ここで確保したメモリがplhsに渡される場合、すなわち確保したメモリを最終的にMATLAB上で利用する場合はMATLABがそのメモリを解放します
逆に言えば、plhsに渡すメモリ部分をMEXの内部で解放してしまうと、MATLABが終了時に同じ部分を解放しようとするため、2重Freeが生じる可能性があります。
要するに、MEX実行後にMATLABで使う部分のメモリは解放しないでね、ということです。また、同様の理由でMATLABから渡された部分のメモリもMEX内部で解放してはいけません。

そのほかにMEXを利用するうえではまりやすいポイントとしては、plhsへデータを渡そうとしたとき、適切に行わないとメモリリークが生じる場合があるという点があります。
具体的な例を考えてみましょう。次のようなmexFunction関数を作ったものとします。

このコード内ではmxSetPr(plhs[0], ptr)plhs[0]ptrの位置を指すようにポインタの上書きを行っており、一見これで問題ないように見えますが、実際は5*5*sizeof(double)のメモリがリークしています。
なぜならば、mxCreateDoubleMatrixで確保したメモリが置き去りになっているからです。
これを回避するためには、mxCreateDoubleMatrixで先にplhs[0]に対応する部分のメモリを確保し、その部分に対応するポインタを利用することで直接plhs[0]内のデータをいじることが考えられます。
これを実現したのが次の例です。

このようにすることで潜在的なメモリリークを回避することができます。

MEXでCUDAを使う

ここで本題のCUDAを使う方法ですが、初めに述べたようにMEXを介してCUDAを使うことになります。
GPUのメモリを使う場合は次のような流れでmexFunctionを記述することになります。

上記のMEXの使い方がわかっていれば、それと同様に使うだけでOKです。
基本的に注意する点としては、ホスト(CPU)側とデバイス(GPU)側のメモリの扱いに注意することくらいでしょうか。
CUDAで注意すべき点と大体同じだと思います。

例:2つのベクトルの線形和

ここでは2つのベクトルの線形和を計算するMEXを紹介したいと思います。
src1, src2という2つのベクトルをそれぞれk1倍、k2倍して足し合わせるという処理です。
この処理を行うCUDAソースファイルを次のように作成します。

次に、MATLAB上でこのソースファイルが置いてあるディレクトリ移動し、mexcuda vec_add.cuを実行することでコンパイルが行われます。
すると、vec_addに対応するMEXファイルが生成されるので、これを実行してあげることでvec_add.cuで記述したカーネル関数を呼び出すことができます。

実際に次のテストコードを実行して性能の計測を行ってみました。

今回はせっかくなので新しく使えるようになった東工大のスーパーコンピュータTSUBAME 3.0を利用してみました。
gpuDeviceを実行してGPUデバイスの情報を表示してみると、
>> gpuDevice

ans =

CUDADevice with properties:

Name: ‘Tesla P100-SXM2-16GB’
Index: 1
ComputeCapability: ‘6.0’
SupportsDouble: 1
DriverVersion: 8
ToolkitVersion: 8
MaxThreadsPerBlock: 1024
MaxShmemPerBlock: 49152
MaxThreadBlockSize: [1024 1024 64]
MaxGridSize: [2.1475e+09 65535 65535]
SIMDWidth: 32
TotalMemory: 1.7067e+10
AvailableMemory: 1.6693e+10
MultiprocessorCount: 56
ClockRateKHz: 1480500
ComputeMode: ‘Default’
GPUOverlapsTransfers: 1
KernelExecutionTimeout: 0
CanMapHostMemory: 1
DeviceSupported: 1
DeviceSelected: 1
となりました。現状販売されている最強のGPGPU用GPUと言ってもいい?Tesla P100が搭載されていますね。

run_test.mの実行結果が次のようになります。
>> run_test
N = 1000000
Elapsed time with MATLAB : 0.001871 sec.
Elapsed time with MEX CUDA : 0.006699 sec.

やはり、ベクトルの線形和程度の簡単な演算ではメモリアロケーションなどの余計な処理部分がネックとなってMATLABビルトインの処理より速くなることはないみたいです。
もう少し複雑な関数で勝負すれば勝てるかもしれないですね。

まとめ

本記事ではMATLABで自作CUDAコードを利用するためにMEXを介したCUDAの利用方法について解説しました。
MEXはメモリ管理部分が少し厄介ですが、正しく理解して使えば有効なツールになると思います。
今回紹介した内容はgithub上で公開しております。こちらに公開しておりますので、興味のある方はぜひご利用ください。
この記事が皆様の参考になれば幸いです。

明日はdango_botくんの「Cheeseの活動報告」の記事となります。お楽しみに!

コメントを残す

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