问题 使用CUDA矢量类型有优势吗?


CUDA提供了内置的矢量数据类型 uint2uint4 等等。使用这些数据类型有什么好处吗?

让我们假设我有一个由两个值A和B组成的元组。将它们存储在内存中的一种方法是分配两个数组。第一个数组存储所有A值,第二个数组存储与A值对应的索引处的所有B值。另一种方法是分配一个类型的数组 uint2。我应该使用哪一个?推荐哪种方式?会员吗? uint3 即 xyz 在记忆中并排居住?


8208
2017-09-09 15:29


起源

看起来很奇怪CUDA不提供内置的向量操作,因为每种着色语言都有,你知道下面的硬件支持它。我看到CUDA API使用它们的唯一地方是纹理读取。这是我对CUDA最大的谜团。 - wcochran
@wcochran: 一切 CUDA中的操作是向量操作:warp中的32个线程中的每一个对应于向量中的一个槽。有足够的硬件/软件支持在多线程模型中呈现给您。 - Hurkyl
点,交叉,添加,子,比例,反射等在哪里......? - wcochran


答案:


这将有点推测,但可能会增加@ ArchaeaSoftware的答案。

我主要熟悉Compute Capability 2.0(Fermi)。对于这种架构,我认为使用矢量化类型没有任何性能优势,除了8位和16位类型。

查看char4的声明:

struct __device_builtin__ __align__(4) char4
{
    signed char x, y, z, w;
};

类型对齐到4个字节。我不知道是什么 __device_builtin__ 确实。也许它会在编译器中引发一些魔力......

对于声明来说,事情看起来有点奇怪 float1float2float3 和 float4

struct __device_builtin__ float1
{
    float x;
};

__cuda_builtin_vector_align8(float2, float x; float y;);

struct __device_builtin__ float3
{
    float x, y, z;
};

struct __device_builtin__ __builtin_align__(16) float4
{
    float x, y, z, w;
};

float2 得到某种形式的特殊待遇。 float3 是一个没有任何对齐的结构 float4 得到16个字节。我不知道该怎么做。

全局内存事务是128个字节,对齐到128个字节。事务总是一次执行完整的扭曲。当warp到达执行内存事务的函数时,比如来自全局内存的32位加载,芯片将在那时执行为warp中所有32个线程所需的事务所执行的事务。因此,如果所有访问的32位值都在一个128字节的行内,则只需要一个事务。如果值来自不同的128字节行,则执行多个128字节的事务。对于每个事务,warp被保持大约600个周期,同时从内存中提取数据(除非它在L1或L2高速缓存中)。

因此,我认为找出哪种方法可以提供最佳性能的关键是考虑哪种方法导致最少的128字节内存事务。

假设内置向量类型只是结构,其中一些具有特殊对齐,使用向量类型会导致值以交错方式存储在内存(结构数组)中。所以,如果warp正在加载所有的 x 此时的值,其他值(yzw由于128字节的事务,将被拉入L1。当warp稍后尝试访问它们时,它们可能不再在L1中,因此必须发出新的全局内存事务。此外,如果编译器能够发出更宽的指令以同时读取更多的值,以备将来使用,它将使用寄存器来存储负载点和使用点之间的值,这可能会增加寄存器的使用内核。

另一方面,如果将值打包到数组结构中,则可以使用尽可能少的事务来为负载提供服务。所以,从阅读时 x 数组,只 x 值在128字节的事务中加载。这可能会导致更少的事务,更少依赖缓存以及计算和内存操作之间更均匀的分配。


7
2017-09-09 22:27



“因此,我认为找出哪种方法可以提供最佳性能的关键是考虑哪种方法导致最少的128字节内存事务。”不必要。您应该观看Paulius的一个演示文稿,例如: bit.ly/OzutxO。增加飞行中的交易通常有助于提高带宽利用率。 - harrism
“因此,如果warp正在加载该点的所有x值,那么其他值(y,z,w)将被拉入L1,因为128字节的事务。当warp稍后尝试访问那些时,它是可能他们不再在L1“。如果从float4数组加载到float4变量(将存储在寄存器中),则在线程使用它们时,您不必担心y,z和w是否在缓存中,因为线程将它们放在寄存器中。对于需要float4数据(或适合其他结构之一)的应用程序,通常是YES,您应该使用结构。 - harrism
@harrism究竟是什么 __builtin_align__(16) 如果是float4的上面的结构? - username_4567
它告诉编译器在16字节对齐的边界上分配struct(或此类结构的数组)。我相信 __builtin_align__ 是一个包装 __align__。您可以通过挖掘CUDA标头进行确认。 __align__ 应该在CUDA C编程指南中描述。 - harrism
@harrism:谢谢你指向演示文稿的指针。我在GTC,但不认为我抓住了任何Paulius的演讲。我会试着去找他们。不知道我在想什么,在加载了矢量类型之后必须依赖缓存......我会删除我的答案,但那时你的评论也会丢失。想要添加答案吗? - Roger Dahl


我不相信CUDA中的内置元组([u] int [2 | 4],float [2 | 4],double [2])具有任何内在优势;它们主要是为了方便而存在您可以使用相同的布局定义自己的C ++类,编译器可以有效地对它们进行操作。硬件确实具有本机64位和128位负载,因此您需要检查生成的微码以确定。

至于你是否应该使用uint2(结构数组或AoS)或两个uint数组(数组结构或SoA)的数组,没有简单的答案 - 它取决于应用程序。对于方便大小(2x32位或4x32位)的内置类型,AoS的优点是您只需要一个指针来加载/存储每个数据元素。 SoA需要多个基本指针,或者每个元素至少需要多个偏移和单独的加载/疮操作;但有时只对元素子集进行操作的工作负载可能会更快。

作为使用AoS产生良好效果的工作负载的示例,请查看nbody示例(使用float4来保存每个粒子的XYZ +质量)。 Black-Scholes样本使用SoA,大概是因为float3是一个不方便的元素大小。


4
2017-09-09 21:44



硬件具有64位和128位负载 和商店。一般结构 喜欢  uint2 和 uint4, 如果 它们对您的数据和算法有意义,是有利的,因为它们可以增加每线程事务大小,从而更有效地使用可用带宽。您可以创建自己的自定义结构,但要确保它们像CUDA提供的结构一样指定对齐方式。 - harrism
@harrism所以,如果我没有错,这就是这样... uint2的所有成员将并排驻留在内存中,因此使用类型为uint2的数组CAN可以减少内存事务,因为它会发现两个值。 - username_4567
是啊。了解如何在CUDA SDK中的nbody,粒子和其他物理演示中使用float4。 - harrism
+1我知道这是一个老帖子,但我认为你的第二和第三段非常重要。通常在并行计算中,我通常会听到SoA更好,但并非总是如nbody示例代码所证明的那样。 - James


有一些很好的信息 在另一个线程中 这与这里所说的大部分主要结论相矛盾。


1
2017-08-15 17:15