CUDA中float3的合并访存问题

文章探讨了在CUDA中处理float3类型数据时遇到的内存访问问题,提出通过扩展为float4存储、利用向量线程进行分块写入等方法来优化合并访存,以提高性能并平衡内存消耗和计算开销。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

缘起

使用CUDA做三维点云计算的时候,需要在单线程中读取float3类型的三维点,做一系列计算,再将计算后的结果写入。写完之后,Nsight Compute总是提醒有 uncoalesced global memmory,对应到行后发现,这个问题出现在float3的读入和写入位置。
众所周知,CUDA支持单次读取1byte, 2bytes,4 bytes, 8 bytes 或16 bytes,但是唯独没有float3类型的12 bytes,想来想要float3类型的数据合并访存是比较麻烦的。在stack overflow上找到一篇不错的文章,链接,本文对该文章做简要翻译。

CUDA架构对合并访存的支持

当前的CUDA 架构支持 1 字节、2 字节、4 字节、8 字节和 16 字节全局内存加载和存储。但不存在一个可用的,12 字节的float3数据的直接加载或存储机制。
float3是存在于 CUDA C++ 语言级别的抽象,硬件实现中没有float3这个概念。CUDA C++中的float3 由三个浮点数组成,因此,访问float3通常会映射到 4 字节加载/存储,它通常由三个 4 字节加载/存储操作完成。线程与线程间的访问(一次float)地址不连续,因此,无法直接合并访存。
例如,对于如下语句:

__global__ void test(float3* dest)
{
    dest[threadIdx.x] = { 1.0f, 2.0f, 3.0f };
}

数据的存储顺序为:

1st store:  xx xx t1 xx xx t2 xx xx t3 xx xx t4 xx xx t5 xx xx t6 …
2nd store:  xx t1 xx xx t2 xx xx t3 xx xx t4 xx xx t5 xx xx t6 xx …
3rd store:  t1 xx xx t2 xx xx t3 xx xx t4 xx xx t5 xx xx t6 xx xx …

其中 t i是warp 的第 ixx个线程,表示跳过的 4 字节地址。如您所见,线程执行的存储之间存在 8 字节间隙。

将float3拓展为float4存储

例如,如下方案添加了一个辅助位,且使用alignas内存对齐

struct alignas(16) Stuff
{
    float3 p;
    int blub;
};

__global__ void test(Stuff* dest)
{
    dest[threadIdx.x].p = { 1.0f, 2.0f, 3.0f };
    dest[threadIdx.x].blub = 42;
}

这样,新的Stuff数据将会通过一次写入指令存储,能减少延时,但问题在于,会增加1/3的内存消耗。这种方式做写入数据的排列为:

t1 t1 t1 t1 t2 t2 t2 t2 t3 t3 t3 t3 t4 t4 t4 t4 …

使用3个向量线程写入float3

例如,使用如下操作:

__global__ void test(float3* dest)
{
    auto i = threadIdx.x % 3;
    auto m = i == 0 ? &float3::x : i == 1 ? &float3::y : &float3::z;
    dest[threadIdx.x / 3].*m = i;
}

此时,每个线程都会对 afloat3的成员之一执行一次存储,并且连续的线程将存储到连续的 4 字节地址,从而实现完美合并的内存访问,存储结果为:

t1 t2 t3 t4 t5 t6 t7 t8 t9 t10 t11 t12 t13 t14 t15 …

使用这种方法的唯一顾虑在于,与最终写入值相关的计算消耗相比,合并访存带来的收益是否更大

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值