我正在尝试编写一个简单的粒子系统,利用CUDA来更新粒子位置。现在我定义一个粒子有一个对象,其位置定义有三个浮点值,一个速度也定义了三个浮点值。更新粒子时,我在速度的Y分量上添加一个常数值来模拟重力,然后将速度加到当前位置以得出新位置。在内存管理方面,最好是维护两个独立的浮点数组来存储数据或以面向对象的方式构造。像这样:
struct Vector
{
float x, y, z;
};
struct Particle
{
Vector position;
Vector velocity;
};
看起来两种方法的数据大小相同(每个浮点数4个字节,每个向量3个浮点数,每个粒子2个向量,总计24个字节)似乎OO方法可以实现更高效的数据传输CPU和GPU,因为我可以使用单个Memory复制语句而不是2(并且从长远来看更多,因为还有一些关于将变得相关的粒子的其他信息,如年龄,寿命,重量/质量,温度等等,然后还有代码的简单可读性和易于处理它也使我倾向于OO方法。但我看到的例子并没有使用结构化数据,因此我想知道是否有理由。
所以问题是哪个更好:单个数据数组或结构化对象?
答案 0 :(得分:18)
在数据并行编程中,常见的是“结构数组”(SOA)与“结构数组”(AOS),其中第一个例子是AOS,第二个是SOA。许多并行编程范例,特别是SIMD风格的范例,都倾向于SOA。
在GPU编程中,通常首选SOA的原因是优化对全局内存的访问。您可以在去年GTC的Advanced CUDA C上查看录制的演示文稿,详细了解GPU如何访问内存。
重点是内存事务的最小大小为32字节,并且您希望最大化每个事务的效率。
使用AOS:
position[base + tid].x = position[base + tid].x + velocity[base + tid].x * dt;
// ^ write to every third address ^ read from every third address
// ^ read from every third address
使用SOA:
position.x[base + tid] = position.x[base + tid] + velocity.x[base + tid] * dt;
// ^ write to consecutive addresses ^ read from consecutive addresses
// ^ read from consecutive addresses
在第二种情况下,从连续地址读取意味着您的效率为100%,而第一种情况为33%。请注意,在较旧的GPU(计算能力1.0和1.1)上,情况要糟糕得多(效率为13%)。
还有另一种可能性 - 如果你在结构中有两个或四个浮点数,那么你可以100%效率地阅读AOS:
float4 lpos;
float4 lvel;
lpos = position[base + tid];
lvel = velocity[base + tid];
lpos.x += lvel.x * dt;
//...
position[base + tid] = lpos;
再次,查看高级CUDA C演示文稿了解详细信息。