最小化64位线程分离共享内存的银行冲突的策略

时间:2018-06-10 19:41:12

标签: cuda 64-bit idiomatic gpu-shared-memory bank-conflict

假设我在CUDA块中有完整的线程warp,并且这些线程中的每一个都用于处理类型为T的N个元素,驻留在共享内存中(因此我们总共有warp_size * N = 32 N个元素)。不同的线程永远不会访问彼此的数据。 (嗯,他们这样做,但是在我们不关心的后期阶段)。此访问将在循环中发生,如下所示:

for(int i = 0; i < big_number; i++) {
    auto thread_idx = determine_thread_index_into_its_own_array();
    T value = calculate_value();
    write_to_own_shmem(thread_idx, value);
}

现在,不同的线程可能每个都有不同的索引,或者相同 - 我没有这样或那样做任何假设。但我确实希望尽量减少共享内存库冲突。

如果sizeof(T) == 4,那么这很容易:只需将所有线程i的数据放在共享内存地址i,32 + i,64 + i,96 + i等中。这样可以放置所有i的数据在同一家银行,这也与其他银行的银行不同。大。

但是现在 - 如果sizeof(T) == 8怎么办?我应该如何放置我的数据并访问它,以便最大限度地减少银行冲突(不知道指数)?

注意:假设T是普通旧数据。你甚至可以认为它是一个数字,如果这使你的答案更简单。

2 个答案:

答案 0 :(得分:2)

tl; dr:使用与32位值相同的交错方式。

在晚于开普勒的微架构(直到Volta)上,我们理论上可以得到的最好的是2个共享内存事务,用于完整的warp读取单个64位值(因为单个事务为每个提供32位)最多的车道。)

这在实践中可通过针对32位数据描述的类似放置模式OP实现。也就是说,对于T* arr,要将iidx个元素读为T[idx + i * 32]。这将编译,以便发生两个事务:

  1. 较低的16个通道从T(利用所有库)的前32 * 4个字节获取数据
  2. 较高的16从T中的连续32 * 4字节(利用所有存储体)获得数据
  3. 因此GPU比分别为每个通道获取4个字节更智能/更灵活。这意味着它可以比早期的答案所提出的简单的“将T分成两半”的想法做得更好。

    (此答案基于@ RobertCrovella的评论。)

答案 1 :(得分:-2)

在Kepler GPU上,这有一个简单的解决方案:只需更改银行大小! Kepler支持动态地将共享内存条大小设置为8而不是4。但是,这个功能在以后的微体系结构中是不可用的(例如Maxwell,Pascal)。

现在,对于最近的CUDA微体系结构,这是一个丑陋且次优的答案:将64位案例减少到32位案例。

  • 而不是每个线程存储类型T的N个值,它存储2N个值,每个连续对是T的低32位和高32位。
  • 要访问64位值,需要进行2次T次访问,T由“

    ”组成。
    uint64_t joined =
        reinterpret_cast<uint32_t&>(&upper_half) << 32  +
        reinterpret_cast<uint32_t&>(&lower_half);
    auto& my_t_value = reinterpret_cast<T&>(&joined);
    

    和写作时的反向相同。

正如评论所示,最好进行64位访问,如this answer中所述。