0

假设我在一个 CUDA 块中有一个完整的线程扭曲,并且这些线程中的每一个都旨在与驻留在共享内存中的 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 的所有数据放在同一个 bank 中,这也是不同的从另一条车道的银行。伟大的。

但是现在——如果sizeof(T) == 8呢?我应该如何放置和访问我的数据,以尽量减少银行冲突(对指数一无所知)?

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

4

2 回答 2

2

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

在晚于 Kepler 的微架构(高达 Volta)上,理论上我们能得到的最好结果是 2 个共享内存事务,用于读取单个 64 位值的完整扭曲(因为单个事务最多为每个通道提供 32 位)。

这在实践中可以通过针对 32 位数据描述的类似布局模式 OP 来实现。也就是说,对于T* arr,让车道将第' 个元素i读取为。这将编译,以便发生两个事务:idxT[idx + i * 32]

  1. 较低的 16 个通道从 T 中的前 32*4 字节获取它们的数据(利用所有存储体)
  2. 较高的 16 位从 T 中的连续 32*4 字节获取数据(利用所有存储体)

因此,GPU 比尝试分别为每个通道获取 4 个字节更智能/更灵活。这意味着它可以比之前提出的简单的“将 T 分成两半”的想法做得更好。

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

于 2018-06-11T07:55:33.173 回答
-2

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

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

  • 它不是每个线程存储 N 个 type 的值T,而是存储 2N 个值,每个连续对是 a 的低 32 位和高 32 位T
  • 要访问 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 位访问,如本答案中所述。

于 2018-06-10T19:46:47.360 回答