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

CUDA是否自动将float4数组转换为数组结构?,cuda,nsight,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;

我有以下代码片段:

#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;
}
#包括
结构无意义{
第3组;
浮动其他;
};
__全局无效合并(float4*float4Array,无意义*nonsenseArray){
float4 somecoordination=float4Array[threadIdx.x];
x=5;
float4Array[threadIdx.x]=somecoordination;
无意义的nonsenseValue=nonsenseArray[threadIdx.x];
无意义值。其他=3;
nonsenseArray[threadIdx.x]=nonsenseValue;
}
int main(){
float4*float4数组;
cudamaloc(&float4Array,32*sizeof(float4));
cudaMemset(float4Array,32*sizeof(float4),0);
无意义*无意义数组;
Cudamaloc(&nonsenseArray,32*sizeof(无意义));
cudaMemset(非感测数组,32*sizeof(无意义),0);
合并(float4Array、nonsenseArray);
cudaDeviceSynchronize();
返回0;
}
<>当我在Nsight运行英伟达剖析器,并查看全局内存访问模式时,FloAT4数组具有完美的合并读写。同时,无意义数组的访问模式很差(因为它是一个结构数组)


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

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

只有一个数组,且该数组的元素仍具有相同顺序的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
数组提供了更好的模式,因为编译器会生成。这有时被称为“向量加载”,因为我们为每个线程加载一个向量(
float4
)。因此,相邻的线程仍在读取相邻的数据,并且具有理想的合并行为。在上面的示例中,线程0将读取
a[0].x
a[0].y
a[0].z
a[0].w
,线程1将读取
a[1].x
a[1].y
等。所有这些都将在单个请求(即SASS指令)中发生,但可能会在多个事务中拆分。将请求拆分为多个事务不会导致任何效率损失(在本例中)

对于
无意义的
结构,编译器无法识别该结构也可以以类似的方式加载,因此在后台,它必须为每个线程生成3或4个加载:

  • 一个8字节加载(或两个4字节加载)来加载
    float3组的前两个字
  • 一个4字节的加载,用于加载
    float3组的最后一个字
  • 一个4字节的加载来加载
    浮点值另一个
如果您映射出每个线程的上述负载,可能使用上面的图表,您将看到每个负载都涉及一个跨步(每个线程加载的项目之间未使用的元素),因此导致效率降低

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

还介绍了与AoS->SoA转换相关的一些想法以及相关的效率增益


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

我不知道矢量加载是一件事。谢谢可能需要注意的是:编译器无法生成用于访问
结构的向量加载/存储的原因是,向量加载/存储要求数据以8或16字节的边界对齐,而对于结构来说,这是不保证的。如果您只是简单地给您的结构提供必要的对齐方式(
struct alignas(16){…};
),那么编译器会将结构成员访问权打包到向量加载/存储中…