6

私はCUDAの初心者で、推力のチュートリアルを読んでいます。単純ですが、ひどく整理されたコードを書いて、推力の加速を理解しようとしています(このアイデアは正しいですか?)。cpuに配列を追加し、gpuにdevice_vectorを追加して、2つのベクトル(10000000 int)を別のベクトルに追加しようとしています。

これがそのことです:

#include <iostream>
#include "cuda.h"
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <thrust/device_vector.h>
#include <thrust/host_vector.h>

#define N 10000000
int main(void)
{
    float time_cpu;
    float time_gpu;
    int *a = new int[N];
    int *b = new int[N];
    int *c = new int[N];
    for(int i=0;i<N;i++)
    {
        a[i]=i;
        b[i]=i*i;
    }
    clock_t start_cpu,stop_cpu;
    start_cpu=clock();
    for(int i=0;i<N;i++)
    {
        c[i]=a[i]+b[i];
    }
    stop_cpu=clock();   
    time_cpu=(double)(stop_cpu-start_cpu)/CLOCKS_PER_SEC*1000;
    std::cout<<"Time to generate (CPU):"<<time_cpu<<std::endl;
    thrust::device_vector<int> X(N);
    thrust::device_vector<int> Y(N);
    thrust::device_vector<int> Z(N);
    for(int i=0;i<N;i++)
    {
        X[i]=i;
        Y[i]=i*i;
    }
    cudaEvent_t start, stop;
    cudaEventCreate(&start);
    cudaEventCreate(&stop);
    cudaEventRecord(start,0);       
    thrust::transform(X.begin(), X.end(),
        Y.begin(),
        Z.begin(),
        thrust::plus<int>());
    cudaEventRecord(stop,0);
    cudaEventSynchronize(stop);
    float elapsedTime;
    cudaEventElapsedTime(&elapsedTime,start,stop);
    std::cout<<"Time to generate (thrust):"<<elapsedTime<<std::endl;
    cudaEventDestroy(start);
    cudaEventDestroy(stop); 
    getchar();
    return 0;
}

CPUの結果は非常に高速に見えますが、私のマシン(i5-2320,4G、GTX 560 Ti)ではGPUの実行速度が非常に遅く、CPU時間は約26、GPU時間は約30です。私は自分のコードの愚かなエラーで推力を間違っただけでしたか?それとももっと深い理由がありましたか?

C ++の新人として、コードを何度もチェックしましたが、GPUで推力を使用すると時間が遅くなりました。そこで、5つの異なるアプローチでvectorAddを計算することの違いを示すためにいくつかの実験を行いました。QueryPerformanceFrequency()統一された時間測定方法としてWindowsAPIを使用しています。

各実験は次のようになります。

f = large_interger.QuadPart;  
QueryPerformanceCounter(&large_interger);  
c1 = large_interger.QuadPart; 

for(int j=0;j<10;j++)
{
    for(int i=0;i<N;i++)//CPU array adding
    {
        c[i]=a[i]+b[i];
    }
}
QueryPerformanceCounter(&large_interger);  
c2 = large_interger.QuadPart;  
printf("Time to generate (CPU array adding) %lf ms\n", (c2 - c1) * 1000 / f);

__global__これがGPU配列を追加するための私の単純な関数です:

__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;
    }
}

関数は次のように呼び出されます。

for(int j=0;j<10;j++)
{
    add<<<(N+127)/128,128>>>(dev_a,dev_b,dev_c);//GPU array adding
}   

ベクトルa[N]とb[N]をベクトルc[N]に追加して、次のように10回ループします。

  1. CPUにアレイを追加
  2. CPUにstd::vectorを追加します
  3. CPUにthrust::host_vectorを追加します
  4. GPUにthrust::device_vectorを追加します
  5. GPUにアレイを追加します。これが結果です

N=10000000の場合

そして私は結果を得る:

  1. 268.992968msを追加するCPUアレイ
  2. CPU std::vector追加1908.013595ms
  3. CPU Thrust::host_vectorが10776.456803msを追加
  4. GPU Thrust::device_vectorが297.156610msを追加
  5. 5.210573msを追加するGPUアレイ

そして、これは私を混乱させました、私はテンプレートライブラリの実装に精通していません。コンテナと生データ構造の間でパフォーマンスは本当に大きく異なりましたか?

4

3 に答える 3

9

実行時間のほとんどは、X[i]とY[i]を初期化するループで費やされています。これは合法ですが、大きなデバイスベクトルを初期化するのは非常に遅い方法です。ホストベクトルを作成し、それらを初期化してから、それらをデバイスにコピーすることをお勧めします。テストとして、次のようにコードを変更します(デバイスベクトルX[i]およびY[i]を初期化するループの直後)。

}  // this is your line of code
std::cout<< "Starting GPU run" <<std::endl;  //add this line
cudaEvent_t start, stop;   //this is your line of code

次に、追加された行が出力された直後にGPUタイミングの結果が表示されることがわかります。したがって、待機している時間はすべて、ホストコードから直接これらのデバイスベクトルを初期化することに費やされます。

これをラップトップで実行すると、CPU時間は約40、GPU時間は約5になるため、実際にタイミングをとっているコードのセクションでは、GPUはCPUの約8倍の速度で実行されます。

XとYをホストベクトルとして作成し、次に類似のd_Xとd_Yデバイスベクトルを作成すると、次のように全体の実行時間が短くなります。

thrust::host_vector<int> X(N);     
thrust::host_vector<int> Y(N);     
thrust::device_vector<int> Z(N);     
for(int i=0;i<N;i++)     
{     
    X[i]=i;     
    Y[i]=i*i;     
}   
thrust::device_vector<int> d_X = X;
thrust::device_vector<int> d_Y = Y;

変換呼び出しを次のように変更します。

thrust::transform(d_X.begin(), d_X.end(),      
    d_Y.begin(),      
    Z.begin(),      
    thrust::plus<int>()); 

これで、CPU実行測定がGPU測定よりも高速であることがわかりました。申し訳ありませんが、結論に飛びつきました。私のラップトップは、2.6GHzコアi7とQuadro1000MGPUを搭載したHPラップトップです。私はcentos6.2linuxを実行しています。いくつかのコメント:GPUで重い表示タスクを実行している場合、パフォーマンスが低下する可能性があります。また、これらをベンチマークする場合、比較に同じメカニズムを使用するのが一般的です。必要に応じて、両方にcudaEventsを使用でき、CPUコードのタイミングをGPUコードと同じにすることができます。また、推力を使用して、時間制限のないウォームアップ実行を実行してから、測定のためにテストを繰り返すのが一般的です。同様に、ループで10回以上テストを実行し、分割して平均を取得するのも一般的な方法です。私の場合、clocks()の測定値はかなり粗いことがわかります。これは、連続して実行すると30、40、または50になるためです。GPU測定では、5.18256のようなものが得られます。これらのいくつかは役立つかもしれませんが、なぜあなたの結果と私の結果が(GPU側で)それほど異なるのか正確には言えません。

OK私は別の実験をしました。コンパイラはCPU側で大きな違いを生みます。-O3スイッチを使用してコンパイルし、CPU時間を0に落としました。次に、CPUタイミング測定値をclocks()メソッドからcudaEventsに変換すると、CPU測定時間は12.4(-O3最適化あり)で、GPUでは5.1のままでした。側。

マイレージは、タイミング方法とCPU側で使用しているコンパイラによって異なります。

于 2012-09-27T15:25:48.467 に答える
1

まず、Y[i]=i*i;10M要素の整数には適合しません。整数はおよそ1e10を保持し、コードには1e14が必要です。

次に、使用しているライブラリに関係なく、変換のタイミングが正しく、CPUよりも高速であるように見えます。この場合、CPUでベクトルを初期化してから、GPUに転送するというRobertの提案が適しています。

第三に、整数の倍数を実行できないため、ベンチマーク用に、floatで同様の処理を行うためのより単純なCUDAライブラリコード(私が取り組んでいるArrayFireを使用)を以下に示します。

int n = 10e6;
array x = array(seq(n));
array y = x * x;
timer t = timer::tic();
array z = x + y;
af::eval(z); af::sync();
printf("elapsed seconds: %g\n", timer::toc( t));

幸運を!

于 2012-09-27T15:39:24.510 に答える
-1

最近、Quadro1000mでCUDAThrustを使用して同様のテストを実行しています。私はthrust::sort_by_keyをベンチマークとして使用してそのパフォーマンスをテストしましたが、結果が良すぎてブーイングを納得させることができません.512MBのペアを並べ替えるのに100ミリ秒以上かかります。

あなたの問題のために、私は2つのことで混乱しています。

(1)なぜこのtime_cpuに1000を掛けるのですか?1000がなければ、それはすでに数秒です。

time_cpu=(double)(stop_cpu-start_cpu)/CLOCKS_PER_SEC*1000;

(2)そして、26、30、40と言うことで、秒またはミリ秒を意味しますか?'cudaEvent'は、経過時間を's'ではなく'ms'で報告します。

于 2013-04-20T17:36:24.320 に答える