GPU が一貫して CPU よりも優れている CPU (g++ を使用) と GPU (nvcc を使用) の両方でコーディングできる、可能な限り最も簡潔な量のコードを探しています。どのタイプのアルゴリズムも受け入れられます。
明確にするために:私は文字通り2つの短いコードブロックを探しています。1つはCPU用(g ++でC ++を使用)、もう1つはGPU(nvccでC ++を使用)用で、GPUが優れています。できれば秒またはミリ秒のスケールで。可能な限り短いコード ペア。
最初に、私のコメントを繰り返します。GPU は高帯域幅、高レイテンシです。ナノ秒のジョブ (またはミリ秒または秒のジョブでさえ) で GPU が CPU を打ち負かそうとすることは、GPU の処理を行うポイントを完全に失っています。以下はいくつかの簡単なコードですが、GPU のパフォーマンス上の利点を本当に理解するには、スタートアップ コストを償却するために大きな問題サイズが必要になります...そうでなければ意味がありません。キーを回し、エンジンを始動し、ペダルを踏むのに時間がかかるという理由だけで、私は 2 フィート レースでフェラーリに勝つことができます。だからといって、私が意味のある意味でフェラーリより速いというわけではありません。
C++ では次のようなものを使用します。
#define N (1024*1024)
#define M (1000000)
int main()
{
float data[N]; int count = 0;
for(int i = 0; i < N; i++)
{
data[i] = 1.0f * i / N;
for(int j = 0; j < M; j++)
{
data[i] = data[i] * data[i] - 0.25f;
}
}
int sel;
printf("Enter an index: ");
scanf("%d", &sel);
printf("data[%d] = %f\n", sel, data[sel]);
}
CUDA/C で次のようなものを使用します。
#define N (1024*1024)
#define M (1000000)
__global__ void cudakernel(float *buf)
{
int i = threadIdx.x + blockIdx.x * blockDim.x;
buf[i] = 1.0f * i / N;
for(int j = 0; j < M; j++)
buf[i] = buf[i] * buf[i] - 0.25f;
}
int main()
{
float data[N]; int count = 0;
float *d_data;
cudaMalloc(&d_data, N * sizeof(float));
cudakernel<<<N/256, 256>>>(d_data);
cudaMemcpy(data, d_data, N * sizeof(float), cudaMemcpyDeviceToHost);
cudaFree(d_data);
int sel;
printf("Enter an index: ");
scanf("%d", &sel);
printf("data[%d] = %f\n", sel, data[sel]);
}
それでもうまくいかない場合は、N と M を大きくするか、256 を 128 または 512 に変更してみてください。
参考までに、時間測定で同様の例を作成しました。GTX 660 では、GPU の速度が 24 倍になり、その操作には実際の計算に加えてデータ転送が含まれます。
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>
#include <time.h>
#define N (1024*1024)
#define M (10000)
#define THREADS_PER_BLOCK 1024
void serial_add(double *a, double *b, double *c, int n, int m)
{
for(int index=0;index<n;index++)
{
for(int j=0;j<m;j++)
{
c[index] = a[index]*a[index] + b[index]*b[index];
}
}
}
__global__ void vector_add(double *a, double *b, double *c)
{
int index = blockIdx.x * blockDim.x + threadIdx.x;
for(int j=0;j<M;j++)
{
c[index] = a[index]*a[index] + b[index]*b[index];
}
}
int main()
{
clock_t start,end;
double *a, *b, *c;
int size = N * sizeof( double );
a = (double *)malloc( size );
b = (double *)malloc( size );
c = (double *)malloc( size );
for( int i = 0; i < N; i++ )
{
a[i] = b[i] = i;
c[i] = 0;
}
start = clock();
serial_add(a, b, c, N, M);
printf( "c[0] = %d\n",0,c[0] );
printf( "c[%d] = %d\n",N-1, c[N-1] );
end = clock();
float time1 = ((float)(end-start))/CLOCKS_PER_SEC;
printf("Serial: %f seconds\n",time1);
start = clock();
double *d_a, *d_b, *d_c;
cudaMalloc( (void **) &d_a, size );
cudaMalloc( (void **) &d_b, size );
cudaMalloc( (void **) &d_c, size );
cudaMemcpy( d_a, a, size, cudaMemcpyHostToDevice );
cudaMemcpy( d_b, b, size, cudaMemcpyHostToDevice );
vector_add<<< (N + (THREADS_PER_BLOCK-1)) / THREADS_PER_BLOCK, THREADS_PER_BLOCK >>>( d_a, d_b, d_c );
cudaMemcpy( c, d_c, size, cudaMemcpyDeviceToHost );
printf( "c[0] = %d\n",0,c[0] );
printf( "c[%d] = %d\n",N-1, c[N-1] );
free(a);
free(b);
free(c);
cudaFree( d_a );
cudaFree( d_b );
cudaFree( d_c );
end = clock();
float time2 = ((float)(end-start))/CLOCKS_PER_SEC;
printf("CUDA: %f seconds, Speedup: %f\n",time2, time1/time2);
return 0;
}
非常に簡単な方法は、たとえば最初の 100,000 個の整数、または大きな行列演算の 2 乗を計算することです。実装が簡単で、分岐を回避したり、スタックを必要としないなど、GPU の強みに役立ちます。しばらく前に OpenCL と C++ でこれを行い、かなり驚くべき結果を得ました。(2GB の GTX460 は、デュアルコア CPU の約 40 倍のパフォーマンスを達成しました。)
サンプル コードをお探しですか、それとも単にアイデアをお探しですか?
編集
40x は、クアッド コアではなく、デュアル コア CPU に対するものでした。
いくつかのポインタ:
@Paul R へのコメント応答で述べたように、OpenCL の使用を検討してください。OpenCL を使用すると、GPU と CPU で同じコードを再実装することなく簡単に実行できるからです。
(これらは、振り返ってみるとおそらくかなり明白です。)
OpenCL はこれをテストするための優れた方法であるという David のコメントに同意します。これは、CPU と GPU で実行中のコードを簡単に切り替えることができるためです。Mac で作業できる場合、Apple には、カーネルが CPU、GPU、またはその両方で実行されているOpenCL を使用して N 体シミュレーションを行うサンプル コードがあります。それらをリアルタイムで切り替えることができ、FPS カウントが画面に表示されます。
もっと単純なケースとして、David が説明したのと同様の方法で平方を計算する「hello world」OpenCL コマンドライン アプリケーションがあります。これはおそらく、あまり手間をかけずに非 Mac プラットフォームに移植できるでしょう。GPU と CPU の使用を切り替えるには、
int gpu = 1;
hello.c ソース ファイルの行を CPU の場合は 0、GPU の場合は 1 に設定します。
Apple は、メインの Mac ソース コード リストに OpenCL のサンプル コードをいくつか追加しています。
David Gohara 博士は、このトピックに関する紹介ビデオ セッションの最後(約 34 分) で、分子動力学計算を実行する際の OpenCL の GPU 高速化の例を示しました。彼の計算では、8 つの CPU コアで実行される並列実装から単一の GPU に移行することで、約 27 倍のスピードアップが見られます。繰り返しますが、これは最も単純な例ではありませんが、実際のアプリケーションと GPU で特定の計算を実行する利点を示しています。
また、OpenGL ES シェーダーを使用して基本的な計算を実行することで、モバイル スペースをいじりました。GPU でシェーダーとして実行すると、この特定のデバイスの CPU で実行される同じ計算よりも、画像全体で単純な色のしきい値処理シェーダーを実行すると、約 14 倍から 28 倍高速であることがわかりました。