标签: data-oriented-design

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

背景

在游戏引擎开发中,我们通常使用面向数据的设计来获得最佳的内存和计算性能.

我们以粒子系统为例.

在粒子系统中,我们有很多粒子,每个粒子可能有几个属性,如位置,速度等.

C++中的典型实现如下:

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

struct ParticleSystem {
    vector<Particle> particles;
    // ...
};
Run Code Online (Sandbox Code Playgroud)

该实现的一个问题是粒子属性彼此交错.此内存布局不是缓存友好的,可能不适合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 …
Run Code Online (Sandbox Code Playgroud)

data-oriented-design rust

6
推荐指数
1
解决办法
1935
查看次数

为什么对这个数组结构体的成员求和比对结构体数组求和要快得多?

我一直在使用https://github.com/google/benchmark和g++ 9.4.0来检查不同场景下数据访问的性能(用“ -O3”编译)。结果令我惊讶。

我的基线是访问std::array(“减少数据”)中的长数。我想添加一个额外的字节数据。一次我创建一个额外的容器(“拆分数据”),一次我在数组中存储一个结构(“组合数据”)。

这是代码:

#include <benchmark/benchmark.h>

#include <array>
#include <random>

constexpr int width  = 640;
constexpr int height = 480;

std::array<std::uint64_t, width * height> containerWithReducedData;

std::array<std::uint64_t, width * height> container1WithSplitData;
std::array<std::uint8_t, width * height>  container2WithSplitData;

struct CombinedData
{
    std::uint64_t first;
    std::uint8_t  second;
};

std::array<CombinedData, width * height> containerWithCombinedData;

void fillReducedData(const benchmark::State& state)
{
    // Variable is intentionally unused
    static_cast<void>(state);

    // Generate pseudo-random numbers (no seed, therefore always the same numbers)
    // NOLINTNEXTLINE
    auto engine …
Run Code Online (Sandbox Code Playgroud)

c++ arrays caching compiler-optimization data-oriented-design

6
推荐指数
1
解决办法
470
查看次数

如何创建高效的静态哈希表?

我需要从中创建中小型静态哈希表.通常,这些将有5-100个条目.当创建哈希表时,所有键哈希都是预先知道的(即键已经是哈希值.)目前,我创建了一个HashMap,这是我对键进行排序所以我得到O(log n)查找3-5平均查找我关心的尺寸.维基百科称,与链接一个简单的哈希表会导致平均的全表3个查找,所以这还不值得我的麻烦(即以散%N作为第一项,并做了链接.)鉴于我知道所有哈希都在前面,似乎应该有一个简单的方法来获得一个快速,静态完美的哈希 - 但我找不到一个好的指针如何.即摊销O(1)访问没有(少?)额外的开销.我该如何实现这样的静态表?

内存使用很重要,因此我需要存储的越少越好.

编辑:请注意,如果我必须手动解决一次碰撞,那就没问题.也就是说,如果我能做一些链接,例如平均有直接访问和最坏情况3的间接,那就没问题.这不是我需要一个完美的哈希.

hashtable data-oriented-design

5
推荐指数
1
解决办法
3706
查看次数

如何处理连续分配中的对象删除?

我最近发现了面向数据设计的好处。它看起来非常令人印象深刻。要点之一是按类型和访问对数据进行分组,不是全部放在对象中,而是放在数组中,以防止缓存未命中并进行更好的处理。

所以在游戏中我们仍然有实例,用户可以销毁它们中的任何一个(不仅仅是数组中的最后一个)。我不知道如何有效地处理数组中间的对象删除。

我有一个想法:要isAlive有价值,但这会对条件数量造成相当大的影响,因为每个对象在处理、绘图、...

另一个想法是移动整个数组以填充必须删除的空间,但这会在删除时消耗大量资源。

人如何在国防部处理这个问题?

所以提出要求:

  • 它必须是数组,以减少 DOD 中描述的缓存未命中
  • 它必须具有快速随机位置对象删除,最大 o(log n)
  • 对象自创建以来就不能移动,因为它们可能在未知的地方被引用,因此会导致程序错误行为

data-oriented-design

5
推荐指数
2
解决办法
836
查看次数

GLSL:OpenGL 缓冲区中的结构数组与数组结构

现在,当阅读 Internet 中的不同资源时,如果您要按顺序处理大型数组,那么数组结构似乎是一种非常高效的数据存储方式。

例如在 C++ 中

struct CoordFrames
{
    float* x_pos;
    float* y_pos;
    float* z_pos;
    float* scaleFactor;
    float* x_quat;
    float* y_quat;
    float* z_quat;
    float* w_quat;
};
Run Code Online (Sandbox Code Playgroud)

允许比数组更快地处理大数组(感谢 SIMD)

struct CoordFrame
{
    glm::vec3 position;
    float scaleFactor;
    glm::quat quaternion;
};
Run Code Online (Sandbox Code Playgroud)

GPU 是专为大规模并行计算而设计的处理器。SIMD 是这里的“必备”。所以结论是数组结构在这里最有用。

但 ...

  • 我从未在任何地方看到过这样的 GLSL 着色器(这对我来说是错误的):

    #define NUM_POINT_LIGHTS 16
    uniform float point_light_x[NUM_POINT_LIGHTS];
    uniform float point_light_y[NUM_POINT_LIGHTS];
    uniform float point_light_z[NUM_POINT_LIGHTS];
    uniform float point_light_radius[NUM_POINT_LIGHTS];
    uniform float point_light_color_r[NUM_POINT_LIGHTS];
    uniform float point_light_color_g[NUM_POINT_LIGHTS];
    uniform float point_light_color_b[NUM_POINT_LIGHTS];
    uniform float point_light_power[NUM_POINT_LIGHTS];
    
    Run Code Online (Sandbox Code Playgroud)

    或类似的东西也不经常看到:

    #define NUM_POINT_LIGHTS 16
    uniform vec3 point_light_pos[NUM_POINT_LIGHTS]; …
    Run Code Online (Sandbox Code Playgroud)

arrays performance gpu glsl data-oriented-design

5
推荐指数
1
解决办法
2694
查看次数

用于SoA/AoS内存布局的C++零成本抽象

假设我有一个使用Array of Structures(AoS)内存布局的大代码.我想在C++中构建一个零成本的抽象,它允许我在尽可能少的重构努力之间切换AoS和SoA.例如,使用具有访问成员函数的类

 struct Item{
   auto& myDouble(){ return mDouble; }
   auto& myChar(){ return mChar; }
   auto& myString(){ return mString; }
 private:
   double mDouble;
   char mChar;
   std::string mString;
 };
Run Code Online (Sandbox Code Playgroud)

它在循环中的容器内使用

std::vector<Item> vec_(1000);
for (auto& i : vec_)
  i.myDouble()=5.;
Run Code Online (Sandbox Code Playgroud)

我想改变第一个片段,而第二个片段保持相似...例如,有类似的东西

MyContainer<Item, SoA> vec_(1000)
for (auto& i : vec_)
  i.myDouble()=5.;
Run Code Online (Sandbox Code Playgroud)

我可以使用"SoA"或"AoS"模板参数选择内存布局.我的问题是:这样的事情存在于某个地方吗?如果没有,最好如何实施?

c++ abstraction design-patterns data-oriented-design template-meta-programming

5
推荐指数
1
解决办法
1132
查看次数

类型应该在面向数据的设计中有方法吗?

目前,我的应用程序包含三种类型的类。它应该遵循面向数据的设计,如果不是,请纠正我。这是三种类型的类。代码示例并不那么重要,您可以根据需要跳过它们。他们只是为了给人留下印象。我的问题是,我应该向我的类型类添加方法吗?

当前设计

类型只是保存值。

struct Person {
    Person() : Walking(false), Jumping(false) {}
    float Height, Mass;
    bool Walking, Jumping;
};
Run Code Online (Sandbox Code Playgroud)

每个模块实现一个独特的功能。它们可以访问所有类型,因为它们是全局存储的。

class Renderer : public Module {
public:
    void Init() {
        // init opengl and glew
        // ...
    }
    void Update() {
        // fetch all instances of one type
        unordered_map<uint64_t, *Model> models = Entity->Get<Model>();
        for (auto i : models) {
            uint64_t id = i.first;
            Model *model = i.second;
            // fetch single instance by id
            Transform *transform = Entity->Get<Transform>(id);
            // …
Run Code Online (Sandbox Code Playgroud)

c++ architecture encapsulation data-oriented-design

4
推荐指数
1
解决办法
1276
查看次数

面向数据设计中的接口

这句话是这样的:

“编程接口/抽象,而不是实现”。

我们都知道接口是面向对象编程中解耦的一种手段。就像某些对象履行的合同一样。

但我无法理解的是:

如何在面向数据的设计中对接口/抽象进行编程?

就像调用一些“Drawable”一样,但我现在不知道它是矩形还是圆形,但它实现了接口“Drawable”。

谢谢

abstraction interface data-oriented-design

3
推荐指数
1
解决办法
1197
查看次数

添加未使用的内存时性能下降

当我偶然发现这种奇怪的性能下降时,我正在玩一个简单的“游戏”来测试面向数据设计的不同方面。

我有这个结构来存储游戏船舶的数据:

constexpr int MAX_ENEMY_SHIPS = 4000000;
struct Ships
{
    int32_t count;
    v2 pos[MAX_ENEMY_SHIPS];
    ShipMovement movements[MAX_ENEMY_SHIPS];
    ShipDrawing drawings[MAX_ENEMY_SHIPS];
    //ShipOtherData other[MAX_ENEMY_SHIPS]; 

    void Add(Ship ship)
    {
        pos[count] = ship.pos;
        movements[count] = { ship.dir, ship.speed };  
        drawings[count] = { ship.size, ship.color };
        //other[count] = { ship.a, ship.b, ship.c, ship.d };
        count++;
    }
};
Run Code Online (Sandbox Code Playgroud)

然后我有一个函数来更新运动数据:

void MoveShips(v2* positions, ShipMovement* movements, int count)
{
    ScopeBenchmark bench("Move Ships");
    for(int i = 0; i < count; ++i)
    {
        positions[i] = positions[i] + (movements[i].dir * movements[i].speed);
    }
} …
Run Code Online (Sandbox Code Playgroud)

c++ optimization data-oriented-design

3
推荐指数
1
解决办法
122
查看次数