CUDA是否会自动将float4数组转换为数组结构?

时间:2018-11-03 00:39:12

标签: cuda nsight

我有以下代码段:

#include <stdio.h>

struct Nonsense {
    float3 group;
    float other;
};

__global__ void coalesced(float4* float4Array, Nonsense* nonsenseArray) {
    float4 someCoordinate = float4Array[threadIdx.x];
    someCoordinate.x = 5;
    float4Array[threadIdx.x] = someCoordinate;

    Nonsense nonsenseValue = nonsenseArray[threadIdx.x];
    nonsenseValue.other = 3;
    nonsenseArray[threadIdx.x] = nonsenseValue;
}

int main() {
    float4* float4Array;
    cudaMalloc(&float4Array, 32 * sizeof(float4));
    cudaMemset(float4Array, 32 * sizeof(float4), 0);

    Nonsense* nonsenseArray;
    cudaMalloc(&nonsenseArray, 32 * sizeof(Nonsense));
    cudaMemset(nonsenseArray, 32 * sizeof(Nonsense), 0);

    coalesced<<<1, 32>>>(float4Array, nonsenseArray);
    cudaDeviceSynchronize();
    return 0;
}

当我通过Nsight中的Nvidia探查器运行此命令并查看“全局内存访问模式”时,float4Array具有完美的合并读写功能。同时,无意义数组具有较差的访问模式(由于它是结构数组)。

NVCC是否会自动将概念上为结构数组的float4数组转换为具有更好的内存访问模式的数组结构?

1 个答案:

答案 0 :(得分:2)

否,它不会将其转换为数组的结构。我认为,如果仔细考虑一下,您将得出结论,编译器几乎不可能以这种方式重组数据。毕竟,传递的是指针。

只有一个数组,并且该数组的元素仍然具有相同顺序的struct元素:

float address (i.e. index):      0      1      2      3      4      5 ...
array element             : a[0].x a[0].y a[0].z a[0].w a[1].x a[1].y ...

但是float4数组提供了更好的模式,因为编译器会生成a single 16-byte load per thread。这有时称为“向量加载”,因为我们正在为每个线程加载一个向量(在这种情况下为float4)。因此,相邻线程仍在读取相邻数据,并且您具有理想的合并行为。在上面的示例中,线程0将读取a[0].xa[0].ya[0].za[0].w,线程1将读取a[1].xa[1].y等。所有这些都将在单个 request (即SASS指令)中进行,但可能会分散在多个交易中。将请求拆分为多个事务不会导致效率降低(在这种情况下)。

在使用Nonsense结构的情况下,编译器无法识别该结构也可以以类似的方式加载,因此在后台,它必须为每个线程生成3或4次加载:

  • 一个8字节的加载(或两个4字节的加载)来加载float3 group的前两个字
  • 一个4字节的加载以加载float3 group的最后一个字
  • 一次加载4个字节即可加载float other

如果按上面的图表绘制出每个线程的上述负载,您会发现每个负载都涉及一个跨步(每个线程加载的项目之间未使用的元素),因此效率较低。

通过在结构中使用仔细的类型转换或联合定义,可以使编译器在一次加载中加载Nonsense结构。

This answer还涵盖了一些有关AoS-> SoA转换和相关效率提高的想法。

This answer涵盖了矢量加载的详细信息。