CUDAでGPGPUをやってみた(簡単なコードを書いてみた)
GPGPU(汎用GPU)を触ってみました。
使用した開発環境はもちろんNVIDIAのCUDA(クーダ)です。
というか、現時点でGPGPUをやろうと思ったらCUDAしか選択の余地はありません。今のGPU市場はNVIDIAの一強だからです。

簡単なコードを書いてみたので、そういうのも交えながら、ご紹介しようと思います。
難しいことはまだやっていません。簡単なことしかやっていませんのでご了承ください^_^;
目次
スポンサーリンク
GPGPU(汎用GPU)について
GPGPU(汎用GPU)とは?
GPGPU(汎用GPU)とは、General-purpose computing on graphics processing units の略です。
これまでGPUというのは画像処理にしか使われていませんでした。
GPUの演算資源を画像処理以外の用途で使おう、というのがGPGPUです。
GPUを演算に使うメリットとは?
GPUはCPUと比べるとスレッドの数が圧倒的に多いのです。
それゆえ、単純な並列計算ではGPUの方が高速で計算できるのです。それもCPUとは比べものにならない速さで計算できます。
ちなみに言っておきますが、CPUはGPUに比べると何もかもダメだというわけではありません。用途の違いです。GPUは単純な計算は得意ですが、複雑な計算は苦手です。そいういうのはCPUが計算します。
なぜGPGPUが注目されているのか?
やはりディープラーニングが流行っているからでしょう。(ディープラーニングについては、もはや説明不要だと思うのでしません。)
ディープラーニングは単純な計算の連続です。それはまさにGPUが得意とする処理なのです。
CUDA(クーダ)とは?
CUDA(クーダ)はNVIDIAが開発しているGPGPUのための統合開発環境です。
みんなが大好きな(?)C言語で開発することができます。(私は大好きですよC言語。。。)
今、GPGPUをやろうと思ったらCUDAしか選択の余地はありません。今のGPU市場はNVIDIAの一強状態だからです。
GPUはAMDも作っていますが、NVIDIAに遅れをとっていてGPGPU向けに開発環境を整える余裕はないのです。
なぜGPGPUをやろうと思ったのか
実は最近、ディープラーニングの勉強を始めました。(上でも述べた通り、これはGPUが大活躍する場面なのです。)
ディープラーニングの勉強をしたいだけならば、フレームワークを導入すれば簡単にできます。代表例はCaffeやTensorFlow、Chainerなどでしょう。
これら導入すればGPUとの連携も自動でやってくれますが・・・。
せっかくならGPGPUの勉強もしてしまおうと思ったわけです・・・。
理由はそれだけですw
開発環境について
開発環境は私の自作PCを使用します。
OSはWindows 10 PROです。
GPUはGTX970です。
@GTX970
10世代が出たので1つ前の型ということになりますが、それでも最強クラスのGPUだと思っています。
CUDAのインストールはとても簡単だった
WindowsのCUDAのインストールはとても簡単でしたね。(MacやLinuxはどうなのか知りません^_^;)
Windowsの場合はCUDA Toolkitというのをダウンロードしてインストールするだけです。(事前にVisual Studioを入れておく必要があるかもしれない)
すると、Visual Studioを起動すると新しいプロジェクトからCUDAのプロジェクトを作成するようになっていました。
とっても簡単ですね。
コードを書いてみた
参考にした本
参考にしたのは「CUDA by Example 汎用GPUプログラミング入門」という本です。
この本はNVIDIAのエンジニアが書いた本です。
難易度としては中級者向けの本というところでしょうか?少なくとも一通り理解するためにはC言語はマスターしている必要があります。
ポインタ(メモリの確保)などはバンバン出てきます。
書いたコード
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>
#include <stdlib.h>
#define N 1000000
__global__ void add(int *a, int *b, int*c)
{
int tid = threadIdx.x + blockIdx.x * blockDim.x;
while (tid < N)
{
c[tid] = a[tid] + b[tid];
tid += blockDim.x * gridDim.x;
}
}
int main(void)
{
int *a, *b, *c;
int *dev_a, *dev_b, *dev_c;
// CPU側でメモリを割り当てる
a = (int *)malloc(N * sizeof(int));
b = (int *)malloc(N * sizeof(int));
c = (int *)malloc(N * sizeof(int));
// GPU側でメモリを割り当てる
cudaMalloc((void**)&dev_a, N * sizeof(int));
cudaMalloc((void**)&dev_b, N * sizeof(int));
cudaMalloc((void**)&dev_c, N * sizeof(int));
// CPU側で配列aと配列bを設定する
for (int i = 0; i < N; i++)
{
a[i] = i;
b[i] = 2 * i;
}
// 配列aと配列bをGPUにコピーする
cudaMemcpy(dev_a, a, N * sizeof(int), cudaMemcpyHostToDevice);
cudaMemcpy(dev_b, b, N * sizeof(int), cudaMemcpyHostToDevice);
add << <128, 128 >> >(dev_a, dev_b, dev_c);
// 配列cをGPUからCPUにコピーする
cudaMemcpy(c, dev_c, N * sizeof(int), cudaMemcpyDeviceToHost);
// 要求した処理をGPUが行ったことを確認する
bool success = true;
for (int i = 0; i < N; i++)
{
if ((a[i] + b[i]) != c[i])
{
printf("Error: %d + %d != %d\n", a[i], b[i], c[i]);
success = false;
}
}
if (success)
{
printf("We did it !\n");
}
// GPU側で割り当てたメモリを開放する
cudaFree(dev_a);
cudaFree(dev_b);
cudaFree(dev_c);
// CPU側で割り当てたメモリを解放する
free(a);
free(b);
free(c);
int i;
scanf("%d", &i);
return 0;
}
書いたコードについて
任意の長さのベクトル和を求めるプログラムです。
マクロ宣言されているNの値がベクトルの長さということになります。
ベクトルの値は35行目のfor文で決まっています。ここを変えれば変更することも可能ですよ。
並列処理が行われていることについて
9行目の__global__で始まっている部分が実際に並列処理を行なっている関数です。
この関数はadd <<<128, 128 >>>(dev_a, dev_b, dev_c);という風に呼び出されています。
<<<128, 128>>>というのは、128ブロック128スレッドで処理するという意味です。
add関数内のint tid = threadIdx.x + blockIdx.x * blockDim.x;というのは、一意な番号を得るために行っている処理です。
そして、同関数内のtid += blockDim.x * gridDim.x;は、同じスレッドで次の演算を行う一意な番号を得るための処理です。
わかってしまえば簡単ですね(^ ^)
今後について
今後については全く未定です。本はまだ読み途中ですので、とりあえず全て読みきろうと思っています。
しかし、今後使う予定がありません。CUDAを勉強しても使う機会がないですからね(´・ω・`)
以上です!
ノシ
スポンサーリンク
関連記事