在CUDA / OpenCL中以哪种方式订购共享2D / 3D阵列以进行1维并行缩减?

时间:2014-12-11 02:01:22

标签: algorithm caching cuda opencl reduction

总体目标

我在二分图上有几个缩减,由两个顶点密集数组和一个密集数组表示,指定边是否存在b / w两者。比方说,两个数组是a0 []和a1 [],所有边都像e [i0] [i1](即从a0中的元素到a1中的元素)。

有~100 + 100个顶点和~100 * 100个边,因此每个线程负责一个边。

任务1:最大减少

对于a0中的每个顶点,我想找到连接到它的所有顶点(在a1中)的最大值,然后反过来相同:将结果分配给数组b0,对于a1中的每个顶点,我想要找到连接顶点的最大b0 [i0]。

要做到这一点,我:

1)加载到共享内存

    #define DC_NUM_FROM_SHARED 16
    #define DC_NUM_TO_SHARED 16
    __global__ void max_reduce_down(
            Value* value1
        , Value* max_value_in_connected
        , int r0_size, int r1_size
        , bool** connected
        )
    {
        int id_from;
        id_from = blockIdx.x * blockDim.x + threadIdx.x;
        id_to   = blockIdx.y * blockDim.y + threadIdx.y;
        bool within_bounds = (id_from < r0_size) && (id_to < r1_size);

        //load into shared memory
        __shared__ Value value[DC_NUM_TO_SHARED][DC_NUM_FROM_SHARED]; //FROM is the inner (consecutive) dimension
        if(within_bounds)
            value[threadIdx.y][threadIdx.x] = connected[id_to][id_from]? value1[id_to] : 0;
        else
            value[threadIdx.y][threadIdx.x] = 0;
        __syncthreads();

        if(!within_bounds)
            return;

2)减少

for(int stride = DC_NUM_TO_SHARED/2; threadIdx.y < stride; stride >>= 1)
{
    value[threadIdx.y][threadIdx.x] = max(value[threadIdx.y][threadIdx.x], dc[threadIdx.y + stride][threadIdx.x]);
    __syncthreads();
}

3)回写

max_value_connected[id_from] = value[0][threadIdx.x];

任务2:最佳 k

类似的问题,但减少仅适用于a0中的顶点,我需要找到 k 最佳候选者是从连接中选择的a1( k 是〜5)

1)我用零元素初始化共享数组,除了第一个地方

int id_from, id_to;
id_from = blockIdx.x * blockDim.x + threadIdx.x;
id_to   = blockIdx.y * blockDim.y + threadIdx.y;

__shared Value* values[MAX_CHAMPS * CHAMPS_NUM_FROM_SHARED * CHAMPS_NUM_TO_SHARED]; //champion overlaps
__shared int* champs[MAX_CHAMPS * CHAMPS_NUM_FROM_SHARED * CHAMPS_NUM_TO_SHARED]; // overlap champions


bool within_bounds = (id_from < r0_size) && (id_to < r1_size);
int i = threadIdx.y * CHAMPS_NUM_FROM_SHARED + threadIdx.x;
if(within_bounds)
{
    values[i] = connected[id_to][id_from] * values1[id_to];
    champs[i] = connected[id_to][id_from] ? id_to : -1;
}
else
{
    values[i] = 0;
    champs[i] = -1;
}

for(int place = 1; place < CHAMP_COUNT; place++)
{
    i = (place * CHAMPS_NUM_TO_SHARED + threadIdx.y) * CHAMPS_NUM_FROM_SHARED + threadIdx.x;
    values[i] = 0;
    champs[i] = -1;
}
if(! within_bounds)
    return;
__syncthreads();

2)减少它

for(int stride = CHAMPS_NUM_TO_SHARED/2; threadIdx.y < stride; stride >>= 1)
{
    merge_2_champs(values, champs, CHAMP_COUNT, id_from, id_to, id_to + stride);
    __syncthreads();
}

3)将结果写回

for(int place = 0; place < LOCAL_DESIRED_ACTIVITY; place++)
    champs0[place][id_from] = champs[place * CHAMPS_NUM_TO_SHARED * CHAMPS_NUM_FROM_SHARED + threadIdx.x];

问题

如何订购(转置)共享阵列中的元素,以便内存访问更好地使用缓存? 在这一点上是否重要,或者我可以从其他优化中获得更多? 如果我需要针对任务2进行优化,那么转置边缘矩阵会更好吗? (据我所知,任务1中存在对称性,所以它并不重要。)

P.S。

我已经延迟了展开循环并在加载时进行了第一次缩减迭代,因为在我探索更简单的方法之前,我认为它太复杂了。

对于任务2,不加载零元素会很好,因为数组永远不需要增长,并且只有在执行 log k 步骤后才开始收缩。这将使共享内存中的 k 倍更紧凑!但我害怕得到的索引数学。

语法和正确性

不寻常的类型只是typedef&#39; ed ints / chars / etc - AFAIK,在GPU中,尽可能地使这些变得紧凑是有意义的。我还没有运行代码,不需要检查索引错误。

此外,我正在使用CUDA,但我也对OpenCL的观点感兴趣,因为我认为最佳解决方案应该是相同的,并且我将来会使用OpenCL。

2 个答案:

答案 0 :(得分:1)

好的,我想我想出来了。

我正在考虑的两个备选方案是减少 y 维度,并且独立于 x 维度,反之亦然( x < / strong>维度是连续的)。在任何情况下,调度程序都能够沿 x 维度将线程组装到warp中,因此可以保证一些连贯性。然而,一致性延伸到经线之外会很棒。此外,由于共享阵列的2D / 3D特性,人们必须将尺寸限制为16或甚至8。

为了确保扭曲内的合并,调度程序必须沿 x 维度组装扭曲。

如果减少 x 维度,则在每次迭代后,warp中活动线程的数量将减半。但是,如果减少 y 维度,则活动扭曲的数量将减半。

所以,我需要减少 y

除非转置(加载)是最慢的,这是一种异常情况。

答案 1 :(得分:-1)

合并缓冲区读取真的很重要;如果不这样做,内核可能会慢32倍。如果能够做到这一点,那么值得做一个重新安排的通行证(当然,重新安排通行证也需要合并,但你可以经常利用共享的本地内存来做到这一点。)