2

OpenMPを使用して並列化した素数を計算するコードがあります。

    #pragma omp parallel for private(i,j) reduction(+:pcount) schedule(dynamic)
    for (i = sqrt_limit+1; i < limit; i++)
    {
            check = 1;
            for (j = 2; j <= sqrt_limit; j++)
            {

                    if ( !(j&1) && (i&(j-1)) == 0 )
                    {
                            check = 0;
                            break;
                    }

                    if ( j&1 && i%j == 0 )
                    {
                            check = 0;
                            break;
                    }

            }
            if (check)
                pcount++;

    }

私はそれをGPUに移植しようとしていますが、上記のOpenMPの例で行ったようにカウントを減らしたいと思います。以下は私のコードですが、間違った結果を出すことは別として、遅いです:

__global__ void sieve ( int *flags, int *o_flags, long int sqrootN, long int N) 
{
    long int gid = blockIdx.x*blockDim.x+threadIdx.x, tid = threadIdx.x, j;
    __shared__ int s_flags[NTHREADS];

    if (gid > sqrootN && gid < N)
            s_flags[tid] = flags[gid];
    else
            return;
    __syncthreads();

    s_flags[tid] = 1;

    for (j = 2; j <= sqrootN; j++)
    {
            if ( gid%j == 0 )
            {
                    s_flags[tid] = 0;
                    break;
            }
    }
    //reduce        
    for(unsigned int s=1; s < blockDim.x; s*=2)
    {
            if( tid % (2*s) == 0 )
            {
                    s_flags[tid] += s_flags[tid + s];
            }
            __syncthreads();
    }
    //write results of this block to the global memory
    if (tid == 0)
            o_flags[blockIdx.x] = s_flags[0];

}

まず、このカーネルを高速化するにはどうすればよいですか。ボトルネックはforループだと思いますが、どのように置き換えるかはわかりません。そして次に、私のカウントは正しくありません。'%'演算子を変更しましたが、いくつかの利点に気づきました。

配列ではflags、2からsqroot(N)までの素数をマークしました。このカーネルでは、sqroot(N)からNまでの素数を計算していますが、{sqroot(N)、N}の各数値を確認する必要があります。 {2、sqroot(N)}の素数で割り切れる。o_flags配列には、各ブロックの部分和が格納されます。


編集:提案に従って、コードを変更しました(syncthreadsに関するコメントについて理解が深まりました)。私はflags配列は必要なく、私の場合はグローバルインデックスだけが機能することに気づきました。この時点で私が懸念しているのは、forループに起因する可能性のあるコードの遅さ(正確さ以上)です。また、特定のデータサイズ(100000)の後、カーネルは後続のデータサイズに対して誤った結果を生成していました。データサイズが100000未満の場合でも、GPU削減の結果は正しくありません(NVidiaフォーラムのメンバーは、データサイズが2の累乗ではないことが原因である可能性があると指摘しました)。したがって、まだ3つの(関連している可能性がある)質問があります-

  1. どうすればこのカーネルを高速化できますか?各tidをループする必要がある場合は、共有メモリを使用することをお勧めしますか?

  2. 特定のデータサイズに対してのみ正しい結果が生成されるのはなぜですか?

  3. どうすれば削減を変更できますか?

    __global__ void sieve ( int *o_flags, long int sqrootN, long int N )
    {
    unsigned int gid = blockIdx.x*blockDim.x+threadIdx.x, tid = threadIdx.x;
    volatile __shared__ int s_flags[NTHREADS];
    
    s_flags[tid] = 1;
    for (unsigned int j=2; j<=sqrootN; j++)
    {
           if ( gid % j == 0 )
                s_flags[tid] = 0;
    }
    
    __syncthreads();
    //reduce
    reduce(s_flags, tid, o_flags);
    }
    
4

1 に答える 1

3

私は素数のふるい分けについて何も知らないと公言していますが、実装しているアルゴリズムが正しいかどうかに関係なく、GPU バージョンには多くの正確性の問題があり、正しく動作しなくなります。

  1. __syncthreads()呼び出しは無条件でなければなりません。分岐の分岐により、同じワープ内の一部のスレッドが呼び出しを実行できなくなる可能性があるコードを記述するのは正しくありません__syncthreads()。基礎となる PTX はbar.sync、PTX ガイドには次のように記載されています。

    バリアは、ワープ内のすべてのスレッドがアクティブであるかのように、ワープごとに実行されます。したがって、ワープ内のいずれかのスレッドが bar 命令を実行すると、ワープ内のすべてのスレッドが bar 命令を実行したかのようになります。ワープ内のすべてのスレッドは、バリアが完了するまで停止し、バリアの到着カウントはワープ サイズ (ワープ内のアクティブなスレッドの数ではありません) だけ増加します。条件付きで実行されるコードでは、すべてのスレッドが同じように条件を評価する (ワープが発散しない) ことがわかっている場合にのみ、bar 命令を使用する必要があります。バリアはワープごとに実行されるため、オプションのスレッド数はワープ サイズの倍数である必要があります。

  2. コードはs_flags、グローバル メモリからいくつかの値を条件付きで読み込んだ後、無条件に 1 に設定されます。確かにそれはコードの意図ではないでしょうか?
  3. このコードには、ふるい分けコードとリダクションの間の同期バリアがありません。これにより、共有メモリの競合が発生し、リダクションから誤った結果が生じる可能性があります。
  4. このコードを Fermi クラス カードで実行することを計画している場合は、共有メモリ配列を宣言volatileして、コンパイラの最適化によって共有メモリの削減が損なわれるのを防ぐ必要があります。

これらを修正すると、コードが機能する可能性があります。パフォーマンスはまったく別の問題です。確かに古いハードウェアでは、整数モジュロ演算は非常に遅く、推奨されませんでした。アトキンのふるいがGPU で素数を高速生成するための有用なアプローチであったことを示唆するいくつかの資料を読んだことを思い出すことができます。

于 2011-09-26T10:11:05.507 に答える