use*_*567 11 cuda abstract-data-type
CUDA提供内置的矢量数据类型uint2,uint4等等.使用这些数据类型有什么好处吗?
让我们假设我有一个由两个值A和B组成的元组.将它们存储在内存中的一种方法是分配两个数组.第一个数组存储所有A值,第二个数组存储与A值对应的索引处的所有B值.另一种方法是分配一个类型的数组uint2.我应该使用哪一个?推荐哪种方式?难道成员uint3即x,y,z在内存中驻留侧方?
这将有点推测,但可能会增加@ ArchaeaSoftware的答案.
我主要熟悉Compute Capability 2.0(Fermi).对于这种架构,我认为使用矢量化类型没有任何性能优势,除了8位和16位类型.
查看char4的声明:
struct __device_builtin__ __align__(4) char4
{
signed char x, y, z, w;
};
Run Code Online (Sandbox Code Playgroud)
类型对齐到4个字节.我不知道是什么__device_builtin__.也许它会在编译器中引发一些魔力......
事情看起来有些奇怪的声明float1,float2,float3和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;
};
Run Code Online (Sandbox Code Playgroud)
float2得到某种形式的特殊待遇.float3是一个没有任何对齐的结构,并且float4对齐到16个字节.我不知道该怎么做.
全局内存事务是128个字节,对齐到128个字节.始终执行事务以进行完整的扭曲.当warp到达执行内存事务的函数时,比如来自全局内存的32位负载,芯片将在那时执行与为warp中的所有32个线程进行服务所需的事务一样多的事务.因此,如果所有访问的32位值都在一个128字节的行内,则只需要一个事务.如果值来自不同的128字节行,则执行多个128字节的事务.对于每个事务,warp被保持大约600个周期,同时从内存中提取数据(除非它在L1或L2高速缓存中).
因此,我认为找出哪种方法提供最佳性能的关键是考虑哪种方法导致最少的128字节内存事务.
假设内置向量类型只是结构,其中一些具有特殊对齐,使用向量类型会导致值以交错方式存储在内存(结构数组)中.所以,如果经加载所有的x在这一点值,其他值(y,z,w)将在被拉至L1,因为128字节的交易.当warp稍后尝试访问它们时,它们可能不再处于L1中,因此必须发出新的全局内存事务.此外,如果编译器能够发出更宽的指令以同时读取更多的值,以备将来使用,它将使用寄存器来存储负载点和使用点之间的值,这可能会增加寄存器的使用内核.
另一方面,如果将值打包到数组结构中,则可以使用尽可能少的事务来为负载提供服务.因此,从x数组中读取时,只会x在128字节的事务中加载值.这可能会导致更少的事务,更少依赖缓存以及计算和内存操作之间更均匀的分配.
我不相信 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 是一种不方便的元素大小。