什么是面向数据的设计?

rye*_*guy 139 data-oriented-design

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

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

Eri*_*eim 264

首先,不要将此与数据驱动设计混淆.

我对面向数据的设计的理解是它是关于组织数据以进行有效处理.特别是关于缓存未命中等.另一方面,数据驱动设计是关于让数据控制你的许多程序行为(Andrew Keith的回答很好地描述).

假设您的应用程序中有球对象,其颜色,半径,弹性,位置等属性.

面向对象的方法

在OOP中你会像这样描述你的球:

class Ball {
  Point  position;
  Color  color;
  double radius;

  void draw();
};
Run Code Online (Sandbox Code Playgroud)

然后你会创建一个这样的球集合:

vector<Ball> balls;
Run Code Online (Sandbox Code Playgroud)

面向数据的方法

在面向数据的设计中,您更有可能编写如下代码:

class Balls {
  vector<Point>  position;
  vector<Color>  color;
  vector<double> radius;

  void draw();
};
Run Code Online (Sandbox Code Playgroud)

你可以看到,没有一个单位代表一个球了.球对象仅隐含存在.

这可以在性能方面具有许多优点.通常我们希望同时对许多球进行操作.硬件通常需要大量连续的内存块才能高效运行.

其次,您可能会执行仅影响部分球属性的操作.例如,如果您以各种方式组合所有球的颜色,那么您希望缓存仅包含颜色信息.但是,当所有球属性存储在一个单位中时,您也将拉入球的所有其他属性.即使你不需要它们.

缓存使用示例

说一个球,每个球占用64个字节,一个点需要4个字节.缓存槽也需要64个字节.如果我想更新10个球的位置,我必须将10*64 = 640字节的内存拉入缓存并获得10个缓存未命中.但是,如果我可以将球的位置作为单独的单位工作,那将只需要4*10 = 40个字节.这适合一次缓存提取.因此,我们只获得1次缓存未命中以更新所有10个球.这些数字是任意的我假设缓存块更大.

但它说明了内存布局如何具有严重影响缓存命中率和性能.随着CPU和RAM速度之间的差异变宽,这只会增加重要性.

如何布局内存

在我的球示例中,我对问题进行了大量简化,因为通常对于任何普通应用,您可能会同时访问多个变量.例如,位置和半径可能会经常一起使用.那你的结构应该是:

class Body {
  Point  position;
  double radius;
};

class Balls {
  vector<Body>  bodies;
  vector<Color>  color;

  void draw();
};
Run Code Online (Sandbox Code Playgroud)

您应该这样做的原因是,如果一起使用的数据放在单独的数组中,则存在竞争缓存中相同插槽的风险.因此加载一个会抛弃另一个.

因此,与面向对象编程相比,您最终创建的类与问题的心理模型中的实体无关.由于数据是根据数据使用情况汇总在一起的,因此您不会总是有合理的名称来为数据导向设计提供类.

与关系数据库的关系

面向数据的设计背后的思想与您对关系数据库的看法非常相似.优化关系数据库还可以涉及更高效地使用缓存,尽管在这种情况下,缓存不是CPU缓存放置在内存中的页面.一个好的数据库设计人员也可能会将不经常访问的数据拆分成一个单独的表,而不是创建一个包含大量列的表,而这些列只有少数列使用过.他也可能选择对某些表进行非规范化,以便不必从磁盘上的多个位置访问数据.就像数据导向设计一样,这些选择是通过查看数据访问模式是什么以及性能瓶颈在哪里来做出的.

  • 它可能.但请记住,整个pos阵列不会被拉入.只有一个缓存行,可能还有一些预取.同样速度.因此,对于他们互相垃圾,每个相应的pos和向量块必须映射到同一个高速缓存行.这当然可以发生,这就是为什么建议将结构中使用的变量放在一起的原因.因此,例如,速度和pos将在一个向量中,而颜色将在另一个向量中. (14认同)
  • 说得好; 我只有一个问题.假设我们有一个结构`struct balls {vector <vec3> pos; vector <vec3> velocity;}`,不会更新每个球的位置实际上会使缓存捶打,因为你在速度矢量和位置矢量之间来回移动(是现代机器和缓存线以及所有这些,这也只是一个例子)? (4认同)
  • 谢谢你,你解释得很好. (3认同)
  • @roe您应该将一起访问的属性组合在一起。属性之间根本不应该有任何依赖关系。所以这个结构会更好`struct balls { vector&lt;color&gt; colors; 矢量&lt;body&gt; 身体;/* 包含位置和速度 */ }`。 (2认同)
  • @danijar我用你的建议更新了解释.我本来可以说更多关于这一点,但那真的只会变成一篇文章. (2认同)
  • 看起来像是从数组对象切换到数组对象。 (2认同)

Ale*_*nko 15

Mike Acton 最近就数据导向设计发表了公开演讲:

我对它的基本总结是:如果你想要性能,那么考虑数据流,找到最有可能与你搞砸的存储层并对其进行优化.Mike专注于L2缓存未命中,因为他正在实时,但我想同样的事情适用于数据库(磁盘读取)甚至Web(HTTP请求).我认为这是一种有用的系统编程方式.

请注意,它并不能免除您对算法和时间复杂性的影响,它只是将您的注意力集中在确定最昂贵的操作类型上,然后您必须以疯狂的CS技能为目标.


小智 12

我只是想指出,Noel正在具体谈论我们在游戏开发中面临的一些具体需求.我认为其他正在进行实时软模拟的部门将从中受益,但它不太可能成为一种能够显着改善一般业务应用的技术.此设置用于确保从底层硬件中挤出最后一点性能.


And*_*ith 5

面向数据的设计是一种应用程序逻辑由数据集而不是过程算法构建的设计。例如

程序性方法。

int animation; // this value is the animation index

if(animation == 0)
   PerformMoveForward();
else if(animation == 1)
  PerformMoveBack();
.... // etc
Run Code Online (Sandbox Code Playgroud)

数据设计方法

typedef struct
{
   int Index;
   void (*Perform)();
}AnimationIndice;

// build my animation dictionary
AnimationIndice AnimationIndices[] = 
  {
      { 0,PerformMoveForward }
      { 1,PerformMoveBack }
  }

// when its time to run, i use my dictionary to find my logic
int animation; // this value is the animation index
AnimationIndices[animation].Perform();
Run Code Online (Sandbox Code Playgroud)

像这样的数据设计促进了数据的使用来构建应用程序的逻辑。它更容易管理,尤其是在视频游戏中,因为视频游戏可能有数千个基于动画或其他因素的逻辑路径。

  • 这其实是不正确的。您将面向数据的设计与数据驱动的设计混淆了。我做了同样的事情,直到我读了诺埃尔的文章并意识到他正在谈论完全不同的事情。 (17认同)
  • 另外,Indice 不是一个词。有“指数”和“指数”之分,有的甚至纵容“指数”,但“指数”从来都不是正确的。 (16认同)