28

GPU が一貫して CPU よりも優れている CPU (g++ を使用) と GPU (nvcc を使用) の両方でコーディングできる、可能な限り最も簡潔な量のコードを探しています。どのタイプのアルゴリズムも受け入れられます。

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

4

4 に答える 4

41

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

于 2011-10-05T16:03:34.723 に答える
4

参考までに、時間測定で同様の例を作成しました。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;
} 
于 2014-09-19T05:55:13.250 に答える
4

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

サンプル コードをお探しですか、それとも単にアイデアをお探しですか?

編集

40x は、クアッド コアではなく、デュアル コア CPU に対するものでした。

いくつかのポインタ:

  • ベンチマークの実行中は、たとえば Crysis を実行していないことを確認してください。
  • CPU 時間を浪費している可能性のある不要なアプリやサービスをすべて停止します。
  • ベンチマークの実行中に、お子様が PC で映画を見始めないようにしてください。ハードウェア MPEG デコードは、結果に影響を与える傾向があります。(自動再生で、2 歳の子供がディスクを挿入して怪盗グルーの月泥棒を開始します。イェーイ。)

@Paul R へのコメント応答で述べたように、OpenCL の使用を検討してください。OpenCL を使用すると、GPU と CPU で同じコードを再実装することなく簡単に実行できるからです。

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

于 2011-10-05T15:05:05.280 に答える
2

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 倍高速であることがわかりました。

于 2011-10-05T20:21:46.650 に答える