假设我在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是普通旧数据。你甚至可以认为它是一个数字,如果这使你的答案更简单。
答案 0 :(得分:2)
在晚于开普勒的微架构(直到Volta)上,我们理论上可以得到的最好的是2个共享内存事务,用于完整的warp读取单个64位值(因为单个事务为每个提供32位)最多的车道。)
这在实践中可通过针对32位数据描述的类似放置模式OP实现。也就是说,对于T* arr
,要将i
行idx
个元素读为T[idx + i * 32]
。这将编译,以便发生两个事务:
因此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中所述。