纹理提取比直接全局访问慢,第7章来自“Cuda by example”一书

时间:2012-10-01 19:25:13

标签: cuda textures

我正在阅读和测试“Cuda By example。通用GPU编程简介”一书中的示例。 在测试第7章中的示例时,相对于纹理内存,我意识到通过纹理缓存访问全局内存要比直接访问慢得多(我的NVIDIA GPU是GeForceGTX 260,计算能力1.3,我正在使用NVDIA CUDA 4.2): / p>

  • 对于256 * 256图像,纹理提取(1D或2D)的每帧时间:93 ms
  • 每帧不使用纹理(仅直接全局访问)的时间为256 * 256:8.5 ms

我已经多次仔细检查了代码,并且我一直在阅读随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
            // ...
         }

1 个答案:

答案 0 :(得分:1)

我一直在用nvvp(NVIDIA profiler)测试结果

结果非常好奇,因为它们表明存在大量纹理缓存未命中(这可能是导致性能不佳的原因)。 即使使用指南“CUPTI_User_GUide”,分析器的结果也会显示难以理解的信息:

  • text_cache_hit:纹理缓存命中数(根据1.3功能,它们仅计入一个SM)。

  • text_cache_miss:纹理缓存未命中数(根据1.3功能,它们仅计入一个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”函数根本没有缓存命中(尽管理想情况下内存是“空间定位”,因为每个线程访问靠近其他近线程加入的内存的内存)。我想这是因为这个函数中的线程不会从其他线程访问内存,这似乎是纹理缓存可用的方式(“空间定位”概念相当混乱)

  • 在1D中有一些缓存命中,在2D情况下对于函数“blend_kernel”有更多缓存命中。我想这是因为在该函数中,任何线程都从其邻居线程访问内存。我无法理解为什么2D中有更多而不是1d。

  • 纹理案例中的持续时间比无纹理案例中的持续时间更长(几乎约一个数量级)。也许与这么多纹理缓存未命中有关。

  • 对于“copy_const”函数,SM有1024次访问,“混合内核”有5120次访问。关系5:1是正确的,因为“blend”中有5次提取,“copy_const”中只有1次提取。无论如何,我无法理解所有这1024个来自哪里:理想情况下,此事件“文本缓存未命中/热”仅占一个SM(我的GeForceGTX 260中有24个)并且它仅考虑warp(32个线程大小)。因此,我每个SM有256个线程/ 32 = 8个warp,每个SM有256个块/ 24 = 10或11个“迭代”,所以我期待80或88个(更多,像sm_cta_launched等其他事件,是每个SM的线程块数,应该在我的1.3设备中支持,始终为0 ......)