我正在尝试编写一个简单的粒子系统,利用CUDA来更新粒子位置.现在我定义一个粒子具有一个定义了三个浮点值的位置的对象,还有一个用三个浮点值定义的速度.当更新粒子时,我为速度的Y分量添加一个恒定值来模拟重力,然后将速度加到当前位置,以获得新的位置.在内存管理方面,最好是维护两个独立的浮动数组来存储数据或以面向对象的方式进行结构化.这样的东西
struct Vector { float x,y,z; }; struct Particle { Vector position; Vector velocity; };
看起来像数据的大小是相同的(每个浮点数为4个字节,每个Vector为3个浮点,总共为24个字节的2个每个粒子向量)似乎OO方法将允许cpu和GPU,因为我可以使用一个单独的内存复制语句而不是2(并且从长远来看,更多的是,有一些关于粒子的信息将会变得相关,比如年龄,寿命,体重/质量,温度等)然后,这些也只是简单的可读性的代码和易于处理它也使我倾向于OO方法.但是我看到的例子不能使用结构化数据,所以它让我想知道这是否是一个原因.
那么问题是哪个更好:单独的数据或结构化对象数组?
解决方法
数据并行编程通常涉及“数组结构”(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演示文稿的详细信息.