3

私は本「Cuda By example. An Introduction to General Purpose GPU Programming」の例を読んでテストしています。第 7 章のサンプルをテクスチャ メモリと比較してテストしたところ、テクスチャ キャッシュを介したグローバル メモリへのアクセスは、直接アクセスよりもはるかに遅いことがわかりました (私の NVIDIA GPU は GeForceGTX 260、計算能力 1.3、NVDIA CUDA 4.2 を使用しています)。

  • 256*256 イメージのテクスチャ フェッチ (1D または 2D) でのフレームあたりの時間: 93 ミリ秒
  • 256*256 のテクスチャを使用しないフレームあたりの時間 (直接グローバル アクセスのみ): 8.5 ミリ秒

コードを何度か再確認し、SDK に付属の「CUDA C プログラミング ガイド」と「CUDA C ベスト プラクティス ガイド」も読んでいますが、問題がよくわかりません。私が理解している限りでは、テクスチャ メモリは、キャッシュ (?) のように見せるための特定のアクセス メカニズムが実装された単なるグローバル メモリです。グローバル メモリへの結合されたアクセスによってテクスチャのフェッチが遅くなるかどうか疑問に思っていますが、確信が持てません。

誰かが同様の問題を抱えていますか?(NVIDIA フォーラムで同様の問題に関するリンクをいくつか見つけましたが、そのリンクは利用できなくなりました。)

関連する部分のみを含むテスト コードは次のようになります。

//#define TEXTURE
//#define TEXTURE2

#ifdef TEXTURE
// According to C programming guide, it should be static (3.2.10.1.1)
static texture<float> texConstSrc;
static texture<float> texIn;
static texture<float> texOut;
#endif

    __global__ void copy_const_kernel( float *iptr
    #ifdef TEXTURE2
     ){
    #else
        ,const float *cptr ) {
    #endif
            // map from threadIdx/BlockIdx to pixel position
            int x = threadIdx.x + blockIdx.x * blockDim.x;
            int y = threadIdx.y + blockIdx.y * blockDim.y;
            int offset = x + y * blockDim.x * gridDim.x;

    #ifdef TEXTURE2
            float c = tex1Dfetch(texConstSrc,offset);
    #else
            float c = cptr[offset];     
    #endif

            if ( c != 0) iptr[offset] = c;
    }

    __global__ void blend_kernel( float *outSrc,
    #ifdef TEXTURE
        bool dstOut ) {
    #else
        const float *inSrc ) {
    #endif
            // map from threadIdx/BlockIdx to pixel position
            int x = threadIdx.x + blockIdx.x * blockDim.x;
            int y = threadIdx.y + blockIdx.y * blockDim.y;
            int offset = x + y * blockDim.x * gridDim.x;
            int left = offset - 1;
            int right = offset + 1;
            if (x == 0) left++;
            if (x == SXRES-1) right--;
            int top = offset - SYRES;
            int bottom = offset + SYRES;
            if (y == 0) top += SYRES;
            if (y == SYRES-1) bottom -= SYRES;

    #ifdef TEXTURE
            float t, l, c, r, b;
            if (dstOut) {
                t = tex1Dfetch(texIn,top);
                l = tex1Dfetch(texIn,left);
                c = tex1Dfetch(texIn,offset);
                r = tex1Dfetch(texIn,right);
                b = tex1Dfetch(texIn,bottom);
            } else {
                t = tex1Dfetch(texOut,top);
                l = tex1Dfetch(texOut,left);
                c = tex1Dfetch(texOut,offset);
                r = tex1Dfetch(texOut,right);
                b = tex1Dfetch(texOut,bottom);
            }
            outSrc[offset] = c + SPEED * (t + b + r + l - 4 * c);
    #else
            outSrc[offset] = inSrc[offset] + SPEED * ( inSrc[top] +
                inSrc[bottom] + inSrc[left] + inSrc[right] -
                inSrc[offset]*4);
    #endif
    }

    // globals needed by the update routine
    struct DataBlock {
        unsigned char *output_bitmap;
        float *dev_inSrc;
        float *dev_outSrc;
        float *dev_constSrc;
        cudaEvent_t start, stop;
        float totalTime;
        float frames;
        unsigned size;
        unsigned char *output_host;
    };
    void anim_gpu( DataBlock *d, int ticks ) {
        checkCudaErrors( cudaEventRecord( d->start, 0 ) );
        dim3 blocks(SXRES/16,SYRES/16);
        dim3 threads(16,16);

    #ifdef TEXTURE
        volatile bool dstOut = true;
    #endif

        for (int i=0; i<90; i++) {
    #ifdef TEXTURE
            float *in, *out;
            if (dstOut) {
                in = d->dev_inSrc;
                out = d->dev_outSrc;
            } else {
                out = d->dev_inSrc;
                in = d->dev_outSrc;
            }
    #ifdef TEXTURE2
            copy_const_kernel<<<blocks,threads>>>( in );
    #else
            copy_const_kernel<<<blocks,threads>>>( in,
                d->dev_constSrc );
    #endif
            blend_kernel<<<blocks,threads>>>( out, dstOut );
            dstOut = !dstOut;

    #else
            copy_const_kernel<<<blocks,threads>>>( d->dev_inSrc,
                d->dev_constSrc );
            blend_kernel<<<blocks,threads>>>( d->dev_outSrc,
                d->dev_inSrc );
            swap( d->dev_inSrc, d->dev_outSrc );
    #endif
        }
            // Some stuff for the events
            // ...
         }
4

1 に答える 1

2

nvvp (NVIDIA プロファイラー) で結果をテストしています。

結果は非常に興味深いもので、多くのテクスチャ キャッシュ ミスがあることを示しています (これがおそらくパフォーマンス低下の原因です)。プロファイラーからの結果には、ガイド「CUPTI_User_GUIde) を使用しても理解しにくい情報も表示されます。

  • text_cache_hit: テクスチャ キャッシュ ヒットの数 (1.3 機能によると、1 つの SM に対してのみ考慮されます)。

  • text_cache_miss: テクスチャ キャッシュ ミスの数 (1.3 機能によると、それらは 1 つの SM についてのみ考慮されます)。

以下は、テクスチャ キャッシュを使用しない 256*256 の例の結果です (関連する情報のみが表示されます)。

名前 期間(ns) Grid_Size Block_Size

"copy_const_kernel(...) 22688 16,16,1 16,16,1

"blend_kernel(...)" 51360 16,16,1 16,16,1

以下は、1D テクスチャ キャッシュを使用した結果です。

名前 期間(ns) Grid_Size Block_Size tex_cache_hit tex_cache_miss

"copy_const_kernel(...)" 147392 16,16,1 16,16,1 0 1024

"blend_kernel(...)" 841728 16,16,1 16,16,1 79 5041

以下は、2D テクスチャ キャッシュを使用した結果です。

名前 期間(ns) Grid_Size Block_Size tex_cache_hit tex_cache_miss

"copy_const_kernel(...)" 150880 16,16,1 16,16,1 0 1024

"blend_kernel(...)" 872832 16,16,1 16,16,1 2971 2149

これらの結果は、いくつかの興味深い情報を示しています。

  • 「copy const」関数の場合、キャッシュ ヒットはまったくありません (理想的には、各スレッドが他のニア スレッドがアクセスするメモリに近いメモリにアクセスするという意味で、メモリは「空間的に配置」されます)。これは、この関数内のスレッドが他のスレッドからメモリにアクセスしないためだと思います。これは、テクスチャ キャッシュを使用できるようにするための方法のようです (「空間的に配置された」概念は非常に紛らわしいです)。

  • 関数「blend_kernel」の場合、1D でいくつかのキャッシュ ヒットがあり、2D の場合はさらに多くのキャッシュ ヒットがあります。その関数内で、任意のスレッドが隣接スレッドからメモリにアクセスするという事実によるものだと思います。1D より 2D の方が多い理由がわかりません。

  • 継続時間は、テクスチャのない場合よりもテクスチャの場合の方が長くなります (約 1 桁近く)。おそらく、非常に多くのテクスチャ キャッシュ ミスに関連しています。

  • 「copy_const」関数の場合、SM には合計 1024 回、「blend kernel」には 5120 回のアクセスがあります。「blend」には 5 つのフェッチがあり、「copy_const」には 1 つのフェッチしかないため、5:1 の関係は正しいです。とにかく、この 1024 がどこから来たのか理解できません。理想的には、このイベント「テキスト キャッシュ ミス/ホット」は 1 つの SM (私の GeForceGTX 260 には 24 あります) のみを説明し、ワープ (32 スレッド サイズ) のみを説明します。したがって、SM ごとに 256 スレッド/32=8 ワープと 256 ブロック/24 = SM ごとに 10 または 11 の「反復」があるため、80 または 88 フェッチ (さらに、sm_cta_launched などの他のイベント) のようなものを期待します。は SM ごとのスレッド ブロックの数で、私の 1.3 デバイスでサポートされるはずですが、常に 0 です...)

于 2012-10-06T09:23:51.820 に答える