【问题标题】:Reduce3 example in CUDA SDKCUDA SDK 中的 Reduce3 示例
【发布时间】:2012-10-29 06:04:38
【问题描述】:

我正在阅读 CUDA SDK 中的缩减优化,但在从 reduce2 到 reduce3 发生的事情之后我遇到了问题:

/*
    This version uses sequential addressing -- no divergence or bank conflicts.
*/
template <class T>
__global__ void
reduce2(T *g_idata, T *g_odata, unsigned int n)
{
    T *sdata = SharedMemory<T>();

    // load shared mem
    unsigned int tid = threadIdx.x;
    unsigned int i = blockIdx.x*blockDim.x + threadIdx.x;

    sdata[tid] = (i < n) ? g_idata[i] : 0;

    __syncthreads();

    // do reduction in shared mem
    for (unsigned int s=blockDim.x/2; s>0; s>>=1)
    {
        if (tid < s)
        {
            sdata[tid] += sdata[tid + s];
        }

        __syncthreads();
    }

    // write result for this block to global mem
    if (tid == 0) g_odata[blockIdx.x] = sdata[0];
}

/*
This version uses n/2 threads --
it performs the first level of reduction when reading from global memory.
*/
template <class T>
__global__ void
reduce3(T *g_idata, T *g_odata, unsigned int n)
{
    T *sdata = SharedMemory<T>();

    // perform first level of reduction,
    // reading from global memory, writing to shared memory
    unsigned int tid = threadIdx.x;
    unsigned int i = blockIdx.x*(blockDim.x*2) + threadIdx.x;

    T mySum = (i < n) ? g_idata[i] : 0;

    if (i + blockDim.x < n)
        mySum += g_idata[i+blockDim.x];

    sdata[tid] = mySum;
    __syncthreads();

    // do reduction in shared mem
    for (unsigned int s=blockDim.x/2; s>0; s>>=1)
    {
        if (tid < s)
        {
            sdata[tid] = mySum = mySum + sdata[tid + s];
        }

        __syncthreads();
    }

    // write result for this block to global mem
    if (tid == 0) g_odata[blockIdx.x] = sdata[0];
}

我无法想象 reduce3 的第一级减少尝试做什么,或者为什么线程数减少了一半。谁能指点一下?

【问题讨论】:

    标签: cuda gpu nvidia reduction


    【解决方案1】:

    两者之间的唯一区别是reduce3在共享内存减少之前执行求和。因此,reduce2 仅从全局内存中加载单个值并将其存储在共享内存中:

    // load shared mem
    unsigned int tid = threadIdx.x;
    unsigned int i = blockIdx.x*blockDim.x + threadIdx.x;
    
    sdata[tid] = (i < n) ? g_idata[i] : 0;
    

    reduce3 加载 两个 值,将它们相加,然后将结果存储在共享内存中:

    unsigned int tid = threadIdx.x;
    unsigned int i = blockIdx.x*(blockDim.x*2) + threadIdx.x;
    
    T mySum = (i < n) ? g_idata[i] : 0;
    
    if (i + blockDim.x < n)
        mySum += g_idata[i+blockDim.x];
    
    sdata[tid] = mySum;
    __syncthreads();
    

    因为标准“2 的幂”减少的第一级是在共享内存减少之前由每个线程完成的,所以所需的线程总数是 reduce2 的一半。您还应该注意,reduce2 中使用的一半线程实际上被浪费了——它们只将数据加载到共享内存中,根本不参与缩减。因此,reduce3 将它们移除并使用更少的线程来执行相同的操作。

    如果您继续查看代码的版本,您会看到这个想法扩展到其逻辑结论,每个线程在将结果存储到共享内存并执行归约之前加载并求和许多值。在内存带宽受限的操作中可以提高效率,例如通过使用更少的线程来减少,允许将每个线程的大部分设置开销分摊到更多的输入值上,并减少内存控制器资源的争用。

    【讨论】:

    • 您好,谢谢您的解释。我想我的具体问题是,我不明白 {blockIdx.x*(blockDim.x*2) + threadIdx.x} 和 {i+blockDim.x} 在视觉上指的是什么。另外,线程数是用户指定的,还是编译器实际上从代码中识别出要使用多少线程?
    • 更具体地说,g_idata[blockIdx.x*(blockDim.x*2) + threadIdx.x] 和 g_idata[blockIdx.x*(blockDim.x*2) + 分别是什么位置threadIdx.x+blockDim.x]?我无法想象这一点。
    • 请注意,在 reduce3 中,每个线程现在加载 两个 值。所以每个块都从全局内存中加载2*BlockDim.x 值。已经做出了一个有点武断的选择,即这些将来自全局内存的同一连续段,即。 start locationstart location + blockDim.x。每个块的start location 需要有2*blockDim.x 字的间距。因此,如果块 0 从 0 读取,块 1 从 2*blockDim.x 读取,块 2 从 4*blockDim.x 读取,块 3 从 6*blockDim.x 读取,等等。每个区块的起始位置是blockIdx.x*(blockDim.x*2)
    • 哦,谢谢你,爪子。我现在有了更好的理解。 Block 0 线程将从 0*BlockDim.x 和 1*BlockDim.x 加载数据,Block 1 线程将从 2*BlockDim.x 和 3*BlockDim.x 加载数据,依此类推。最后是 n/2 个线程从 n 个位置加载数据。然后在每个块内减少值。而在 reduce2 中,线程 n/2 到 n-1 只是将数据加载到共享内存中,以后什么也不做!
    • @user1782063:如果我的回答解决了你的问题,也许你会好心accept it。这标志着问题已回答并已完成。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-14
    • 1970-01-01
    • 2011-03-04
    • 2011-09-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多