そうか、symbolを使えばconstantメモリにデータを格納できるのか。symbol系の転送命令ならちゃんとoffsetあるわ。
Archive for the ‘GPU関連’
CUDAのメモリ操作には何かが足りない
オフセット指定が無い。
つまり、GPU上に大きなメモリを確保した場合は、それを送受信する際にCPU側にも同じサイズのメモリを確保する必要がある、と。
……何か間違ってないか?
よくよく考えてみると、OpenGLやDirectXでGPUとデータをやりとりする場合はテクスチャを確保して適当にデータを送受信していた。ここではオフセット調整ができた。CUDAにもテクスチャがあるので関数リファレンスを参照してみると、オフセットの調整が出来そうに見える。なるほど、こっちを使えばいいのか。
ここまで読んだところで、CUDAにはLocalMemory,SharedMemory,GlobalMemory,ConstantMemory,TextureMemoryという多数のメモリが存在しているわけだが、どれが何メガ使えるのかを把握していなかったことに気がついた。うーむ、まだまだだな。
glDeleteFramebuffersEXTで(ry
データサイズ間違えてた。そりゃセグメントエラーもするわな。orz
glDeleteFramebuffersEXTでセグメントエラーする件
おかしいなあ。
何かを忘れているんだろうか。
CUDAのatomic命令が使えない?
なんてこった、G80系ミドルレンジ以下はCUDAのマイナーバージョンが上がって機能が増えていたのか。
並列処理屋さんとしてはatomic系の命令が欲しいんだけどねえ。誤魔化す手段はそれなりにあるわけだが。
CUDAの挙動を追いかけ中:共有メモリマルチスレッドプログラミングの実験みたいなもの
共有メモリに同時多数アクセスをしたらどうなるのか。
- __global__ void test
- (
- int size
- , int* nums
- )
- {
- int id = threadIdx.x + threadIdx.y*10;
- __shared__ int data[100];
- data[0] = id;
- nums[id] = data[0];
- }
適当なサイズの共有メモリを確保して、いわゆるスレッド番号みたいなものを書き込んで、取得してみる。
同一の共有メモリにみんなで書き込んでいるため、値がどうなるかわかったもんじゃない。
共有メモリマルチスレッドプログラミングのバグの温床。
のはずなんだけど、値が狂わない。おかしいなあ?
CPUプログラミングで言うとことのスレッドであるCUDAのブロックは互いに同期を取れないらしいんだけど、これって一般的な並列プログラミングの足枷になるよなあ……。協調処理がしにくすぎる。アプリケーションとの相性になるよなぁ。
CUDAの並列実行モデル?についてのメモ
自分のための備忘録。というか自己解釈。間違ってる部分もあるかも。
GPUへ投げる``ジョブ''の単位は``Grid''。
``Grid''は複数の``Block''で構成される。
1つの``Block''は1つの``Multiprocessor''上で実行される。
(``Block''が更に分割されて``Multiprocessor''に割り当てられることは無い。)
既存のCPUプログラミングにおけるスレッド的な位置づけだが、``Block''間の同期は取れない。
``Multiprocessor''の数が並列実行可能な``Block''の数に対応。
``Block''の数が``Multiprocessor''の数を超えた場合は、
適当な``Multiprocessor''に複数の``Block''が割り当てられ、
タイムスライシングで平行実行される。
``Block''は複数の``Thread''で構成される。
``Thread''は既存のCPUプログラミングで言うところのSIMD的な位置づけ。
感覚的にはむしろ、OpenMPのループ並列に近く感じられる。
``Block''内で同時実行可能な``Thread''の数は8(=Multiprocessorを構成するプロセッサの数)。
warp size=32はどこに関係するんだろう?
うーむ、HWのモデルとメモリのモデルとがうまく脳内にマッピングしきれない。
とりあえず実行時間ベースでの調査に進むか。
CUDAでN-QUEENやってみた:修正版
前回のが思いっきりミスしてたので修正。
CUDAのスレッドってのはいわゆるSIMD。同じ処理を同時にやるだけ。探索で意味があるのは同時に別の処理を行える並列処理。前回のヤツだとうまく並列高速化ができていない。
っつーわけで修正。
GPUカーネルのthreadIdxをblockIdxに変更して、呼び出し側はthreadを1,1,1、gridをN,1,1に変更。
- 逐次実行N=12 9800msec
- 偽並列実行N=12 2300ms
- 真並列実行N=12 870ms
クソ速いな。
ちなみに実験機はQuadroFX4600、CUDAで言うところの「マルチプロセッサ」、要するに演算器のまとまりが12組ある。これは8800GTSと同数で、8800GTX/Ultraは16。そのためか、実装によってはN=13で実行するとうまくいかない。このへんの数の制限みたいなのはまだちょっと脳内で混乱が生じているので、ちゃんと把握しないといかんな。
それにしても、こんなに簡単に10倍以上の性能を拝めるのは、OpenMPプログラムをプロセッサ数16以上というややレアなスーパーコンで動かすときくらいのものだと思うのですよ。素晴らしいなぁ。
CUDAでN-QUEENやってみた
主に、CUDAでプログラムを組むとこんな感じだよーってのをチェックするため。こんなもの高速化しても論文になりませ……ならないと思います。多分。
とりあえず問題サイズと同じだけの並列化*もやってちょうど200行くらい。少なくともシェーダを叩くよりはやりやすいと思う。メモリの扱いだけ気をつければ、単純なCで書ける。まあそのメモリの扱いがめんどいんだけどさ。
(* : N=8の時は8並列。要するに、IDがnの人は1列目のクイーンをn行目に置くと仮定してネ、っていう並列化。)
問題サイズを12より大きくするとこけるのはなんだろう。とりあえずでっちあげただけなのでちゃんと調査してません。もしかしたらスレッド(CUDAの実行モデルで言うところの一番小さい単位。スレッドを束にしたブロックとかいう単位もある。まぁいわゆるスレッド並列化のスレッドと考えていいよ。)の何らかの制限を突破してるかもね。メモリ量とか時間とか。シェーダみたいにループの回数制限とかあったっけかなぁ?
ちなみに実行時間を測定したところ、CPUの非並列非再帰版より明らかに遅かった。更に細かく並列化して高速化する余地というかアイディアはありまくりなので、この後もうちょっと頑張って高速化するかもね。しないかもね。
一応フルソース載せておきます。参考になるかは知らない。とっとと帰ってゼミ資料でも捏造して寝ます。CUDAたんと戯れていたら東急ストアの特売日逃したよチクショウ!
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
- #include <math.h>
- #include <unistd.h>
- #include <cutil.h>
- #define N 12
- __global__ void test(
- int size,
- int* numAnswers,
- int* E,
- int* SE,
- int* NW,
- int* answer,
- int* etc
- )
- {
- int id = threadIdx.x;
- if(id>size){
- if(id<size*2){
- etc[id] = -1;
- }
- return;
- }
- etc[id] = 1;
- if(threadIdx.y>0){
- return;
- }
- int offset1 = id*size;
- int offset2 = id*(size*2-1);
- int x, y;
- x=0;
- y=id;
- answer[offset1+x] = y;
- E[offset1+y] = 1;
- SE[offset2+(size-1)-x+y] = 1;
- NW[offset2+x+y] = 1;
- x++;
- y=0;
- while(1){
- if(x==size){
- // all OK
- //printAnswer(d);
- numAnswers[id]++;
- x--;
- y = answer[offset1+x];
- //removeQueen(d,x,y);
- E[offset1+y] = 0;
- SE[offset2+(size-1)-x+y] = 0;
- NW[offset2+x+y] = 0;
- y++;
- }else if(y==size){
- x--;
- if(x<1){
- break;
- }
- y = answer[offset1+x];
- //removeQueen(d,x,y);
- E[offset1+y] = 0;
- SE[offset2+(size-1)-x+y] = 0;
- NW[offset2+x+y] = 0;
- y++;
- }else{
- int test=0;
- if(E[offset1+y] || SE[offset2+(size-1)-x+y] || NW[offset2+x+y]){
- test=0;
- }else{
- answer[offset1+x] = y;
- E[offset1+y] = 1;
- SE[offset2+(size-1)-x+y] = 1;
- NW[offset2+x+y] = 1;
- test=1;
- }
- if(test==1){
- x++;
- y=0;
- }else{
- y++;
- }
- }
- }
- //etc[id] = offset;
- }
- void
- runTest(int argc, char** argv)
- {
- CUT_DEVICE_INIT();
- printf("%d Byte\n", (1+N+N*N+(N*(N*2-1))+(N*(N*2-1))+N*N)*4);
- // CPU側のデータを準備
- int size;
- int numAnswers[N];
- int E[N*N];
- int SE[N*(N*2-1)];
- int NW[N*(N*2-1)];
- int answer[N*N];
- int etc[N*2];
- int i;
- size = N;
- for(i=0; i<N; i++){
- numAnswers[i] = 0;
- }
- for(i=0; i<N*2; i++){
- etc[i] = 0;
- }
- for(i=0; i<N*N; i++){
- E[i] = 0;
- answer[i] = 0;
- }
- for(i=0; i<N*(N*2-1); i++){
- SE[i] = 0;
- NW[i] = 0;
- }
- // GPU側のメモリを準備
- int* d_numAnswers;
- int* d_E;
- int* d_SE;
- int* d_NW;
- int* d_answer;
- int* d_etc;
- CUDA_SAFE_CALL(cudaMalloc((void**)&d_numAnswers, sizeof(int)*N));
- CUDA_SAFE_CALL(cudaMalloc((void**)&d_E, sizeof(int)*N*N));
- CUDA_SAFE_CALL(cudaMalloc((void**)&d_SE, sizeof(int)*(N*2-1)*N));
- CUDA_SAFE_CALL(cudaMalloc((void**)&d_NW, sizeof(int)*(N*2-1)*N));
- CUDA_SAFE_CALL(cudaMalloc((void**)&d_answer, sizeof(int)*N*N));
- CUDA_SAFE_CALL(cudaMalloc((void**)&d_etc, sizeof(int)*N*2));
- CUDA_SAFE_CALL(cudaMemcpy(d_numAnswers, &numAnswers, sizeof(int)*N, cudaMemcpyHostToDevice) );
- CUDA_SAFE_CALL(cudaMemcpy(d_E, E, sizeof(int)*N*N, cudaMemcpyHostToDevice) );
- CUDA_SAFE_CALL(cudaMemcpy(d_SE, SE, sizeof(int)*(N*2-1)*N, cudaMemcpyHostToDevice) );
- CUDA_SAFE_CALL(cudaMemcpy(d_NW, NW, sizeof(int)*(N*2-1)*N, cudaMemcpyHostToDevice) );
- CUDA_SAFE_CALL(cudaMemcpy(d_answer, &answer, sizeof(int)*N*N, cudaMemcpyHostToDevice) );
- CUDA_SAFE_CALL(cudaMemcpy(d_etc, &etc, sizeof(int)*N*2, cudaMemcpyHostToDevice) );
- // 実行のためのパラメタを準備
- dim3 threads(N, 1, 1);
- dim3 grid(1,1,1);
- // 実行
- unsigned int hTimer;
- CUT_SAFE_CALL( cutCreateTimer(&hTimer) );
- CUT_SAFE_CALL( cutResetTimer(hTimer) );
- CUT_SAFE_CALL( cutStartTimer(hTimer) );
- test<<<grid, threads>>>(size, d_numAnswers, d_E, d_SE, d_NW, d_answer, d_etc);
- CUDA_SAFE_CALL( cudaThreadSynchronize() );
- CUT_SAFE_CALL( cutStopTimer(hTimer) );
- double gpuTime = cutGetTimerValue(hTimer);
- printf("Time: %f ms\n", gpuTime);
- // GPUの処理に問題が起きていないかの確認
- CUT_CHECK_ERROR("Kernel execution failed");
- // 演算結果の取得
- CUDA_SAFE_CALL(cudaMemcpy(&numAnswers, d_numAnswers, sizeof(int)*N, cudaMemcpyDeviceToHost) );
- CUDA_SAFE_CALL(cudaMemcpy(&etc, d_etc, sizeof(int)*N, cudaMemcpyDeviceToHost) );
- // 演算結果の確認
- int nAll=0;
- for(i=0; i<N*2; i++){
- printf(" %d", etc[i]);
- }
- printf("\n");
- for(i=0; i<N; i++){
- nAll += numAnswers[i];
- printf(" %d\n", numAnswers[i]);
- }
- printf("sum %d\n", nAll);
- // クリーンアップ
- //free();
- CUDA_SAFE_CALL(cudaFree(d_numAnswers));
- CUDA_SAFE_CALL(cudaFree(d_E));
- CUDA_SAFE_CALL(cudaFree(d_SE));
- CUDA_SAFE_CALL(cudaFree(d_NW));
- CUDA_SAFE_CALL(cudaFree(d_answer));
- CUDA_SAFE_CALL(cudaFree(d_etc));
- }
- int
- main(int argc, char** argv)
- {
- runTest(argc, argv);
- CUT_EXIT(argc, argv);
- }
HelloWorld@CUDA Runtime API版
というわけで再挑戦。
nvidiaのサンプルに倣って、gpuの処理を書いたgpukernel.cuをcuda.cuからincludeしてみた。
まずはCPUの処理を記述しているcuda.cu。
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
- #include <math.h>
- #include <cutil.h>
- // サンプルの真似をしてGPUの計算を一応別ファイル化
- #include "gpukernel.cu"
- void
- runTest(int argc, char** argv)
- {
- CUT_DEVICE_INIT();
- int nSize = 10;
- int n;
- n = sizeof(float)*nSize;
- // CPU側のデータを準備
- float *h_sd1;
- h_sd1 = (float*)malloc(n);
- int i;
- for(i=0; i<nSize; i++){
- h_sd1[i] = (float)(rand()%100)/10.0f;
- }
- // 値の確認
- for(i=0; i<nSize; i++){
- printf(" %f", h_sd1[i]);
- }
- printf("\n");
- // GPU側のメモリを準備
- float* d_sd1;
- CUDA_SAFE_CALL(cudaMalloc((void**)&d_sd1, n));
- CUDA_SAFE_CALL(cudaMemcpy(d_sd1, h_sd1, n, cudaMemcpyHostToDevice) );
- // 演算結果取得のためのメモリを準備
- float *h_sd2;
- h_sd2 = (float*)malloc(n);
- float* d_sd2;
- CUDA_SAFE_CALL(cudaMalloc((void**)&d_sd2, n));
- // 実行のためのパラメタを準備
- dim3 threads(nSize, 1, 1);
- dim3 grid(1,1,1);
- // 実行
- test<<<grid, threads>>>(d_sd2, d_sd1, nSize, 2.0f);
- // GPUの処理に問題が起きていないかの確認
- CUT_CHECK_ERROR("Kernel execution failed");
- // 演算結果の取得
- CUDA_SAFE_CALL(cudaMemcpy(h_sd2, d_sd2, n, cudaMemcpyDeviceToHost) );
- // 演算結果の確認
- for(i=0; i<nSize; i++){
- printf(" %f", h_sd2[i]);
- }
- printf("\n");
- // クリーンアップ
- free(h_sd1);
- free(h_sd2);
- CUDA_SAFE_CALL(cudaFree(d_sd1));
- CUDA_SAFE_CALL(cudaFree(d_sd2));
- }
- int
- main(int argc, char** argv)
- {
- runTest(argc, argv);
- CUT_EXIT(argc, argv);
- }
続いてGPUの処理を書いたgpukernel.cu
- // CPUから呼び出す関数
- // 配列に定数を掛けているだけの簡単なものです
- __global__ void
- test(float* fOut, float* fIn, int nSize, float f)
- {
- int i;
- for(i=0; i<nSize; i++){
- fOut[i] = fIn[i] * f;
- }
- }
ああ、これは楽チンだ。めんどくさい処理が全部消えてくれました。
コンパイルもnvccにcuda.cuを喰わせれば終了。例によってlibcutil.aとlibGLUT.aのリンクが必要だけど。
gridとthreadsという例のアレがまだ把握できていないのは問題として、とりあえずこれならサクサク組めそうです。
さて、もうちょっと色々といじってみるか。