标签: data-oriented-design

什么是面向数据的设计?

我正在阅读这篇文章,这个人继续谈论如何通过与OOP混合使用面向数据的设计,每个人都能从中受益匪浅.但是,他没有显示任何代码示例.

我搜索了这个,但是找不到任何关于这是什么的真实信息,更不用说任何代码示例了.有人熟悉这个术语并且可以提供一个例子吗?对于别的东西,这可能是一个不同的词吗?

data-oriented-design

139
推荐指数
4
解决办法
6万
查看次数

实践中面向数据的设计?

还有一个问题是关于什么是面向数据的设计,并且有一篇文章经常被提及(我已经读了5到6次).我理解这个的一般概念,特别是在处理例如3d模型时,你想要将所有顶点保持在一起,而不是用法线污染你的脸等.

但是,我确实很难想象数据导向设计如何适用于除最简单的案例(3d模型,粒子,BSP树等)以外的任何其他设计.是否有任何好的例子真正包含数据导向设计并展示了它在实践中如何运作?如果需要,我可以通过大型代码库.

我特别感兴趣的是"哪里有一个很多"的咒语,我似乎无法与其他人联系.是的,总有不止一个敌人,但是,你仍然需要单独更新每个敌人,因为他们现在不会以同样的方式移动它们吗?这同样适用于在接受的答案上述问题的"balls',例如(其实我到这个问题的答案评论问这一点,但还没有得到答复还).仅仅是渲染只需要位置,而不是速度,而游戏模拟需要两者,而不是材料?或者我错过了什么?也许我已经理解了它,这是一个比我想象的更直接的概念.

任何指针将非常感谢!

data-oriented-design

16
推荐指数
1
解决办法
6390
查看次数

了解缓存友好,面向数据的对象和句柄

using namespace std;
Run Code Online (Sandbox Code Playgroud)

考虑实体/对象管理的传统OOP方法:

struct Entity { bool alive{true}; }

struct Manager {        
    vector<unique_ptr<Entity>> entities; // Non cache-friendly

    void update() {
        // erase-remove_if idiom: remove all !alive entities
        entities.erase(remove_if(begin(entities), end(entities),
            [](const unique_ptr<Entity>& e){ return !e->alive; }));
    }
};

struct UserObject {
    // Even if Manager::entities contents are re-ordered
    // this reference is still valid (if the entity was not deleted)
    Entity& entity;
};
Run Code Online (Sandbox Code Playgroud)

但是,我想尝试一种面向数据的方法:动态分配Entity实例,而是将它们存储在缓存友好的线性内存中.

struct Manager {
    vector<Entity> entities; // Cache-friendly
    void …
Run Code Online (Sandbox Code Playgroud)

c++ handle data-oriented-design

16
推荐指数
2
解决办法
3415
查看次数

Javascript中的内存布局 - 面向数据与面向对象的设计

来自C/C++的背景,关于减少缓存未命中的对象的内存布局是特别在处理控制台时尤其重要的.面向数据的设计通常比面向对象的设计更受青睐,以帮助保持相关对象在内存中彼此靠近(特别是在性能关键区域).

最近,我一直在做一些Javascript开发,我想知道Javascript社区中普遍的共识是什么.

由于我在Javascript方面的经验有限,我经常惊讶于在分析时看到完全出乎意料的结果.从浏览器到浏览器,Javascript对象/结构的内部存储器布局和实现变化很大,我想知道是否值得尝试优化.

我在jsPerf上创建了一个简单的测试用例(http://jsperf.com/object-vs-data)来比较这两种方法的性能,虽然它显示了Chrome的性能提升,但是Safari没有明显的加速.

在Javascript中,我是否应该关注对象的内存布局?或者它更像是"以一种方式实施,然后根据需要进行优化"类型的东西?

第二种选择似乎是浪费(就开发时间而言),特别是如果有一些好的指导方针可以遵循.

谢谢〜

补充信息:这基本上是我如何在Javascript中实现这两种方法.上面的jsPerf测试用例就是这样实现的.

var objectOriented = [
    { foo: 1, bar: 2 },
    { foo: 3, bar: 4 }
];

var dataOriented = {
    foos: [1, 3],
    bars: [2, 4]
};

// Object-oriented access:
var a = objectOriented[0].bar;

// Data-oriented access:
var b = dataOriented.bars[0];
Run Code Online (Sandbox Code Playgroud)

javascript oop memory-layout data-oriented-design

15
推荐指数
2
解决办法
2373
查看次数

我对AoS vs SoA的理解是对吗?

我最近一直在阅读AoS vs SoA结构设计和面向数据的设计.很难找到关于这两者的信息,而且我发现的东西似乎比我拥有更多的处理器功能.也就是说,我对前一个主题的理解特别导致了一些我认为应该能够理解答案的问题.

首先,为了确保我的理解不是基于错误的前提,我对AoS vs SoA的功能和利弊的理解,应用于具有'Name'和'Age'字段的'Person'记录的集合与他们相关:

阵列的结构

  • 将数据存储为由多个数组组成的单个结构,例如,将People字段Names作为字符串Ages数组和整数数组作为对象.
  • 信息,说,第三人的名单将通过类似给予People.Names[2]People.Ages[2]
  • 优点:
    • 当仅处理来自许多"人"记录的一些数据时,只需要从内存加载该数据.
    • 所述数据以同类方式存储,允许在大多数此类情况下通过SIMD指令更好地使用高速缓存.
  • 缺点: - 当需要一次访问多个字段时,上述优点就会消失. - 访问一个或几个对象的所有数据变得效率较低. - 大多数编程语言需要更冗长,更难以读/写的代码,因为没有明确的"Person"结构.

结构数组

  • 将数据存储为多个结构,每个结构都有一整套字段,它们本身存储在所有这些结构的数组中,例如对象People数组Person,它们具有Name字符串字段和Age整数字段.
  • 对于第三人的信息会被像被赋予People[2].NamePeople[2].Age
  • 优点:
    • 代码围绕一个更简单的心理模型构建,间接被抽象掉.
    • 单个记录易于访问和使用.
    • Person结构的存在使得在大多数编程语言中编写代码变得更加简单.
  • 缺点:
    • 当处理来自大量记录的一些数据时,需要将整组结构加载到包括不相关数据的存储器中.
    • 结构阵列不是均匀的,在这种情况下限制了SIMD指令可以提供的优点.

它的长短似乎是,假设为了论证,你的性能瓶颈是数据访问和编码的简易性是无关紧要的,如果你几乎完全需要一次访问大量的单个字段数据SoA可能更具性能,而如果您经常需要从同一个对象访问多个字段或处理单个对象而不是一次处理多个字段,AoS将更具性能.

也就是说,我一直在阅读的一些内容似乎让图片变得混乱.首先,多个消息来源已经声明SoA需要索引寻址,据称这是低效的.我无法理解这一点,也无法找到任何解释.在我看来,AoS和SoA需要完全相同的操作来访问任何特定的数据,尽管顺序不同,除了SoA需要一个额外的指针(可能多于一个,取决于所使用的结构类型).稍微简化一下,为了在AoS下面的上面例子中得到第五个人的年龄,你首先得到指向数组的指针,向它添加4,在数组的那个元素处获取结构指针,添加一个大小字符串指向它,因为age是第二个字段,然后访问该指针处的整数.在SoA下,您将获得指向结构的指针并向其添加字符串数组指针的大小以获取年龄列表,然后获取指向存储在那里的整数列表的指针并向其添加4,然后获取整数存储在那里.

其次,我不清楚SoA的好处在多大程度上取决于特定的CPU架构.一方面,我对上述优点的理解并不依赖于任何特定的体系结构,除了SIMD指令在某些情况下可以提供AoS下无法提供的额外好处.另一方面,我看到声称可以限制SoA的优势,具体取决于特定SIMD架构中可用的通道数量.同样,这似乎只会影响SIMD指令可以提供的更多通用缓存优势的额外好处.

最后,我已经看到SoA在遍历数据时需要更多缓存方式的说法.我不完全确定缓存方式是什么或者什么,如果有的话,特别是'遍历'数据.我最好的猜测是"缓存方式"指的是关联缓存中潜在冲突的数量或与之相关,并且它与上面提到的第二个Con相关.

memory caching sse simd data-oriented-design

15
推荐指数
1
解决办法
3104
查看次数

面向数据访问多个索引数据阵列

我正在为游戏引擎开发实体组件系统.我的目标之一是使用面向数据的方法来实现最佳数据处理.换句话说,我想遵循指导而不是希望结构的结构而不是结构数组.但是,我的问题是我还没有想出一个巧妙的方法来解决这个问题.

到目前为止,我的想法是系统中的每个组件都负责游戏逻辑的特定部分,比如重力组件根据质量,速度等来处理每帧的计算力,而其他组件则负责其他组件.因此,每个组件都对不同的数据集感兴趣.重力组件可能对质量和速度感兴趣,而碰撞组件可能对边界框和位置等感兴趣.

到目前为止,我想我可以拥有一个数据管理器,每个属性保存一个数组.因此,假设实体可能具有权重,位置,速度等中的一个或多个,并且它们将具有唯一ID.数据管理器中的数据将表示如下,其中每个数字代表一个实体ID:

weightarray ->   [0,1,2,3]
positionarray -> [0,1,2,3]
velocityarray -> [0,1,2,3]
Run Code Online (Sandbox Code Playgroud)

如果所有实体都具有每个属性,则此方法很有效.但是,如果只有实体0和2具有所有树属性,而其他属性是不移动类型的实体,则它们将没有速度并且数据看起来如下:

weightarray ->   [0,1,2,3]
positionarray -> [0,1,2,3]
velocityarray -> [0,2]     //either squash it like this
velocityarray -> [0  ,2  ]     //or leave "empty gaps" to keep alignment
Run Code Online (Sandbox Code Playgroud)

突然间,迭代它并不容易.如果我采用第二种方法,那么只对迭代和操纵速度感兴趣的组件必须以某种方式跳过空隙.保持阵列短路的第一种方法在更复杂的情况下也不能很好地工作.假设我有一个具有所有三个属性的实体0,另一个实体1仅具有权重和位置,以及实体2仅具有位置和速度.最后,最后一个实体3只有重量.压扁的阵列看起来像:

weightarray ->   [0,1,3]
positionarray -> [0,1,2]
velocityarray -> [0,2]
Run Code Online (Sandbox Code Playgroud)

另一种方法会留下这样的空白:

weightarray ->   [0,1, ,3]
positionarray -> [0,1,2, ]
velocityarray -> [0, ,2, ]
Run Code Online (Sandbox Code Playgroud)

如果您只想迭代仅具有一些属性的实体集,则这两种情况都是重要的迭代.例如,给定的组件X将对处理具有位置和速度的实体感兴趣.如何提取可迭代的数组指针以使该组件进行计算?我想给它一个数组,其中元素彼此相邻,但这似乎是不可能的.

我一直在考虑解决方案,例如为每个阵列设置一个位字段,描述哪些点有效以及哪些是间隙,或者是将数据复制到没有空洞然后被提供给组件的临时阵列的系统,以及其他想法但没有我想到的是优雅的并且没有额外的处理开销(例如额外检查数据是否有效,或额外的数据复制).

我在这里问,因为我希望你们中的某个人可能有类似的经历,或者可能有想法或想法有助于解决这个问题.:)此外,如果这整个想法是垃圾,不可能正确,你有一个更好的想法,请告诉我.希望这个问题不会太长或太杂乱.

谢谢.

c++ data-oriented-design

14
推荐指数
4
解决办法
2598
查看次数

面向数据的树遍历没有递归

我有一个这样的树结构:一个模型有一个根节点,每个节点有任意数量的子节点和任意数量的网格.

在我的应用程序中,很多时候都花费遍历这个树并进行计算,如视图截顶剔除和矩阵乘法.目前,它是天真地实现的,其中每个节点具有子节点和网格的向量,并且递归地遍历树.这很慢.

我一直在关注面向数据的设计,我喜欢它对缓存非常友好的想法.我一直在想这样的事情:

struct Mesh
{
    // misc data
    MeshID mMeshID;
}

// probably needs more information?
struct Node
{
    // begin and end index into Models 'mNodes'
    uint32_t mChildrenBegin;
    uint32_t mChildrenEnd;

    // as above but for meshes
    uint32_t mMeshesBegin;
    uint32_t mMeshesEnd;
}

struct Model
{
    std::vector<Node> mNodes;
    std::vector<Mesh> mMeshes;
}
Run Code Online (Sandbox Code Playgroud)

现在我需要遍历树以获得可见网格列表.在每个节点,我必须检查节点是否可见.以下分支机构:

  • 节点可见.它下面的所有子节点和网格也是可见的.不要深入这个分支.检查相同深度的其他节点.
  • 节点不可见.此节点或其下方没有子节点或网格可见.不要深入这个分支.检查相同深度的其他节点.
  • 该节点部分可见.某些节点和/或某些网格是可见的.必须深入到层次结构中.

树是静态的.在应用程序中加载模型后,树永远不会更改.所以我必须能够使用这些信息来获得有效的结构.

我很困惑如何处理这个问题.

几个问题;

  1. 如何在内存中布局节点?是第一个索引的根节点吗?是否根据深度添加了其他节点?
  2. 如何在不使用递归的情况下迭代树?除非我真的需要,否则我不想访问每个节点.例如,如果深度= 2的节点不可见,则不应测试其所有网格和子节点(及其网格),而是完全跳过.

c++ tree memory-management game-engine data-oriented-design

13
推荐指数
1
解决办法
1983
查看次数

了解std :: transform以及如何击败它

我试图在一个简单的特定问题上理解面向数据的设计.如果我正在做一些非常愚蠢的事情,请提前向面向数据的设计人员道歉,但我很难理解为什么以及我的推理失败的原因.

假设我有一个简单的操作,,float_t result = int_t(lhs) / int_t(rhs).如果我将所有变量保存在相应的容器中,例如,std::vector<float_t>std::vector<int_t>,并且我使用std::transform,我得到正确的结果.然后,对于一个具体的例子,其中using float_t = floatusing int_t = int16_t我假定包装内的这些变量struct,在一个64位架构,并加以收集在容器内应该产生更好的性能.

我认为struct组成一个64位对象,并且单个内存访问struct将给我所需的所有变量.另一方面,当所有这些变量都收集在不同的容器中时,我将需要三种不同的内存访问来获取所需的信息.以下是我设置环境的方法:

#include <algorithm>
#include <chrono>
#include <cstdint>
#include <iostream>
#include <vector>

using namespace std::chrono;

template <class float_t, class int_t> struct Packed {
  float_t sinvl;
  int_t s, l;
  Packed() = default;
  Packed(float_t sinvl, int_t s, int_t l) : sinvl{sinvl}, s{s}, l{l} {}
  void comp() …
Run Code Online (Sandbox Code Playgroud)

c++ data-oriented-design stl-algorithm

11
推荐指数
1
解决办法
1121
查看次数

哪个缓存最友好?

我正在努力抓住面向数据的设计,以及如何最好地编写缓存.基本上有两种情况我无法确定哪种更好以及为什么 - 有一个对象向量或带有对象原子数据的几个向量更好吗?

A)对象矢量示例

struct A
{
    GLsizei mIndices;
    GLuint mVBO;
    GLuint mIndexBuffer;
    GLuint mVAO;

    size_t vertexDataSize;
    size_t normalDataSize;
};

std::vector<A> gMeshes;

for_each(gMeshes as mesh)
{
    glBindVertexArray(mesh.mVAO);
    glDrawElements(GL_TRIANGLES, mesh.mIndices, GL_UNSIGNED_INT, 0);
    glBindVertexArray(0);

    ....
}
Run Code Online (Sandbox Code Playgroud)

B)具有原子数据的载体

std::vector<GLsizei> gIndices;
std::vector<GLuint> gVBOs;
std::vector<GLuint> gIndexBuffers;
std::vector<GLuint> gVAOs;
std::vector<size_t> gVertexDataSizes;
std::vector<size_t> gNormalDataSizes;

size_t numMeshes = ...;

for (index = 0; index++; index < numMeshes)
{
    glBindVertexArray(gVAOs[index]);
    glDrawElements(GL_TRIANGLES, gIndices[index], GL_UNSIGNED_INT, 0);
    glBindVertexArray(0);

    ....
}
Run Code Online (Sandbox Code Playgroud)

哪一个更有内存效率和缓存友好性,导致更少的缓存未命中和更好的性能,为什么?

c++ opengl caching memory-management data-oriented-design

10
推荐指数
1
解决办法
1234
查看次数

在结构阵列(AoS)和阵列结构(SoA)之间来回切换

在许多关于面向数据的设计的着作中起着突出作用的一个特征是,在很多情况下,而不是AoS(结构数组):

struct C_AoS {
  int    foo;
  double bar;
};

std::vector<C_AoS> cs;
...
std::cout << cs[42].foo << std::endl;
Run Code Online (Sandbox Code Playgroud)

在SoA(数组结构)中安排一个数据更有效:

struct C_SoA {
  std::vector<int>    foo;
  std::vector<double> bar;
};

C_SoA cs;
...
std::cout << cs.foo[42] << std::endl;
Run Code Online (Sandbox Code Playgroud)

现在我正在寻找的是一个解决方案,它允许我在不改变调用接口的情况下在AoS和SoA之间切换,即我可以用最小的努力并且没有额外的运行时成本(至少到了过度的间接点),cs[42].foo;无论我正在使用哪种数据排列,都要调用.

我应该注意上面的示例语法是理想的情况,这可能是不可能的,但我也对近似近似非常感兴趣.任何接受者?

c++ data-oriented-design c++11 c++14

9
推荐指数
1
解决办法
1062
查看次数