如何在Rust中实现面向数据的设计?

n6lpvg4x  于 2022-11-12  发布在  其他
关注(0)|答案(1)|浏览(139)

背景

在游戏引擎开发中,我们通常使用面向数据的设计来优化内存和计算性能。
让我们以粒子系统为例。
在一个粒子系统中,我们有很多粒子,每个粒子可能有几个属性,如位置、速度等。
C++中的典型实现如下所示:

struct Particle {
    float positionX, positionY, positionZ;
    float velocityX, velocityY, velocityZ;
    float mass;
    // ...
};

struct ParticleSystem {
    vector<Particle> particles;
    // ...
};

这种实现的一个问题是粒子属性彼此交错。这种存储器布局不适合高速缓存,并且可能不适合SIMD计算。
相反,在面向数据的设计中,我们编写以下代码:

struct ParticleAttribute {
    size_t size;
    size_t alignment;
    const char* semantic;
};

struct ParticleSystem {
    ParticleSystem(
        size_t numParticles,
        const ParticleAttribute* attributes,
        size_t bufferSize) {
        for (size_t i = 0; i < numAttributes; ++i) {
            bufferSize += attributes[i].size * numParticles;
            // Also add paddings to satisfy the alignment requirements.
        }
        particleBuffer = malloc(bufferSize); 
    }

    uint8* getAttribute(const char* semantic) {
        // Locate the semantic in attributes array.
        // Compute the offset to the starting address of that attribute.
    }

    uint8* particleBuffer;      
};

现在我们只有一个分配,并且每个属性都连续驻留在内存中。为了模拟粒子,我们可以编写以下代码:

symplecticEuler(ps.getAttribute("positionX"), ps.getAttribute("velocityX"), dt);

getAttribute函数将获取特定属性的起始地址。

问题

我想知道如何在Rust中实现这一点。
我的想法是首先创建一个名为ParticleSystem的类,它需要几个ParticleAttribute来计算总的缓冲区大小,然后为缓冲区分配内存。我认为这可以在Rust安全代码中完成。
下一步是实现getAttribute函数,该函数将返回一个对特定属性起始地址的引用。我需要你的帮助。我如何获得带有偏移量的原始地址,并将其转换为所需的类型(如float*),然后将该原始指针 Package 为Rust中的可变引用?
另外,我想我应该把这个原始指针 Package 成一个数组的可变引用,因为我需要使用SIMD库通过这个引用加载四个元素。
更新:提供更多关于属性的信息。属性的数量和详细信息在运行时确定。属性的类型可以变化,但我认为我们只需要支持基本的(f32,f64,int,...)。

kmb7vmvb

kmb7vmvb1#

这是一种非常复杂的实现DOD的方式,而且对getter使用运行时查找的想法让我感到畏缩。
简单的版本是为每个属性分配一个内存:

struct Particles {
    x: Vec<f32>,
    y: Vec<f32>,
}

这需要预先知道属性。
然后就没有什么诡计得到所有的y,他们只是坐在那里,已经打好了,等着你。
将其扩展到动态确定的属性并不那么复杂:

  • 我们可以使用HashMap<String, xxx>在运行时查找给定的属性
  • 我们可以使用一个enum来将一个Value存储在散列Map中,散列Map可以采用多种形式(另一种解决方案是使用一个特征)

这变成:


# [derive(Debug, Hash, PartialEq, Eq)]

enum Value {
    UniformInt(i64),
    UniformFloat32(f32),
    UniformFloat64(f64),
    DistinctInt(Vec<i64>),
    DistinctFloat32(Vec<f32>),
    DistinctFloat64(Vec<f64>),
}

struct Particles {
    store: HashMap<String, Value>,
}

我们也可以使用6个散列Map......但是,除非事先知道类型是什么(当唯一的东西是字符串时),否则必须一次一个地查看所有散列Map:烦人而且浪费时间。

相关问题