这有点投机,但可能会增加@ArchaeaSoftware 的答案。
我主要熟悉 Compute Capability 2.0 (Fermi)。对于这种架构,我认为使用矢量化类型没有任何性能优势,除了 8 位和 16 位类型。
查看 char4 的声明:
struct __device_builtin__ __align__(4) char4
{
signed char x, y, z, w;
};
类型对齐到 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;
};
float2得到某种形式的特殊待遇。float3是一个没有任何对齐的结构,并float4对齐到 16 个字节。我不知道该怎么做。
全局内存事务为 128 字节,与 128 字节对齐。事务总是一次执行一个完整的warp。当一个 warp 到达一个执行内存事务的函数时,比如从全局内存中加载 32 位,芯片将在那时执行为服务于 warp 中的所有 32 个线程所需的事务。因此,如果所有访问的 32 位值都在单个 128 字节行内,则只需要一个事务。如果值来自不同的 128 字节行,则执行多个 128 字节事务。对于每个事务,warp 会暂停大约 600 个周期,同时从内存中获取数据(除非它在 L1 或 L2 缓存中)。
因此,我认为找出哪种方法提供最佳性能的关键是考虑哪种方法导致最少的 128 字节内存事务。
假设内置向量类型只是结构,其中一些具有特殊对齐方式,使用向量类型会导致值以交错方式存储在内存(结构数组)中。因此,如果 warp 正在加载x该点的所有值,其他值 ( y, z, w) 将因为 128 字节事务而被拉入 L1。当 warp 稍后尝试访问这些时,它们可能不再位于 L1 中,因此必须发出新的全局内存事务。此外,如果编译器能够发出更广泛的指令以同时读取更多值,以供将来使用,它将使用寄存器来存储加载点和使用点之间的值,这可能会增加寄存器的使用的内核。
另一方面,如果将值打包到数组结构中,则可以使用尽可能少的事务处理负载。因此,从x数组中读取时,只有x值被加载到 128 字节事务中。这可能会导致更少的事务、更少的对缓存的依赖以及计算和内存操作之间的更均匀分布。