9

我正在尝试编写一个简单的粒子系统,利用 CUDA 来更新粒子位置。现在我正在定义一个粒子有一个对象,它的位置用三个浮点值定义,速度也用三个浮点值定义。更新粒子时,我在速度的 Y 分量中添加一个常数值以模拟重力,然后将速度添加到当前位置以得出新位置。在内存管理方面,最好维护两个单独的浮点数组来存储数据或以面向对象的方式构建。像这样的东西:

struct Vector
{
    float x, y, z;
};

struct Particle
{
    Vector position;
    Vector velocity;
};

似乎数据的大小与任何一种方法都相同(每个浮点数 4 个字节,每个向量 3 个浮点数,每个粒子 2 个向量,总共 24 个字节)似乎 OO 方法将允许 CPU 和 CPU 之间更有效的数据传输GPU,因为我可以使用单个内存复制语句而不是 2 个(从长远来看更多,因为还有一些其他有关粒子的信息将变得相关,例如年龄、寿命、重量/质量、温度等)然后还有代码的简单可读性和易于处理,这也使我倾向于 OO 方法。但是我看到的例子没有使用结构化数据,所以我想知道是否有原因。

所以问题是哪个更好:单独的数据数组或结构化对象?

4

1 回答 1

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;

再次,查看 Advanced CUDA C 演示文稿以了解详细信息。

于 2010-02-01T20:26:31.730 回答