web-dev-qa-db-ja.com

CUDAを使用してGPUがCPUより優れていることを示す最も簡単な例

CPU(g ++を使用)とGPU(nvccを使用)の両方に対してコーディングできる、可能な限り最も簡潔なコードを探しています。GPUは常にCPUよりも優れています。どのタイプのアルゴリズムも許容されます。

明確にするために、私は文字通り2つの短いコードブロックを探しています。1つはCPU(g ++でC++を使用)用で、もう1つはGPU(nvccでC++を使用)用で、GPUのパフォーマンスが優れています。好ましくは、秒またはミリ秒のスケールで。可能な最短のコードペア。

27
Chris Redford

まず、私のコメントを繰り返します。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に変更してみてください。

37
Patrick87

非常に非常に単純な方法は、たとえば最初の100,000の整数、または大きな行列演算の二乗を計算することです。簡単に実装でき、分岐を回避したり、スタックを必要としないなど、GPUの強みを活かしたりできます。しばらく前にOpenCLとC++を使用してこれを実行し、かなり驚くべき結果を得ました。 (2GB GTX460はdualコアCPUの約40倍のパフォーマンスを達成しました。)

サンプルコードを探していますか、それとも単なるアイデアを探していますか?

編集

40xはクアッドコアではなく、デュアルコアCPUでした

いくつかのポインタ:

  • ベンチマークの実行中は、Crysisなどを実行していないことを確認してください。
  • CPU時間を盗んでいる可能性のあるすべての不要なアプリとサービスを撃ち落とします。
  • ベンチマークの実行中に、子供がPCで映画を見始めないようにしてください。ハードウェアMPEGデコードは結果に影響を与える傾向があります。 (自動再生では、ディスクを挿入することで、2歳のDespicable Meを開始できます。

@Paul Rに対するコメントレスポンスで述べたように、OpenCLを使用することを検討してください。OpenCLを使用すると、GPUとCPUで同じコードを簡単に再実装できます。

(これらはおそらく振り返ってみるとかなり明白です。)

3
3Dave

CPUでの実行コードとGPUでの実行コードを簡単に切り替えることができるため、OpenCLがこれをテストする優れた方法であるというDavidのコメントに同意します。 Macで作業できる場合、Appleには、OpenCLを使用して N体シミュレーションを実行するサンプルコードの素敵なビットがあります 、CPU、GPUでカーネルを実行しています、または両方。リアルタイムで切り替えることができ、FPSカウントが画面に表示されます。

より単純なケースでは、Davidの説明と同様の方法で平方を計算する "hello world" OpenCLコマンドラインアプリケーション があります。それはおそらく、それほどの労力なしでMac以外のプラットフォームに移植できます。 GPUとCPUの使用量を切り替えるには、

int gpu = 1;

hello.cソースファイルの行をCPUの場合は0、GPUの場合は1に変更します。

Appleは、 メインMacソースコードリスト にOpenCLのサンプルコードをいくつか追加しています。

デビッドゴハラ博士は、 このトピックに関する紹介ビデオセッションの最後に分子動力学計算を実行するときのOpenCLのGPU高速化の例を示しました (約34分)。彼の計算では、8つのCPUコアで実行されている並列実装から1つのGPUに移行することで、約27倍のスピードアップが見られます。繰り返しますが、これは最も単純な例ではありませんが、実際のアプリケーションと、GPUで特定の計算を実行する利点を示しています。

また、 基本的な計算を実行するためにOpenGL ESシェーダーを使用してモバイル空間でいくつかのいじくりを行いました 。画像全体で実行される単純なカラーしきい値シェーダーは、GPUでシェーダーとして実行すると、この特定のデバイスのCPUで実行される同じ計算よりも約14〜28倍高速であることがわかりました。

2
Brad Larson

参考までに、時間測定を使用して同様の例を作成しました。 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;
} 
2
Tae-Sung Shin