什么是面向数据的设计?

时间:2009-10-29 04:13:33

标签: data-oriented-design

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

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

5 个答案:

答案 0 :(得分:250)

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

我对面向数据的设计的理解是,它是关于组织数据以进行有效处理。特别是在高速缓存未命中等方面。数据驱动设计另一方面是让数据控制你的很多程序行为(Andrew Keith's answer所述)。

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

面向对象的方法

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

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

  void draw();
};

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

vector<Ball> balls;

面向数据的方法

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

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

  void draw();
};

正如你所看到的,没有一个单位代表一个球了。球对象仅隐含存在。

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

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

缓存使用示例

说一个球,每个球占用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();
};

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

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

与关系数据库的关系

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

答案 1 :(得分:14)

Mike Acton最近公开谈论了Data oriented design

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

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

答案 2 :(得分:12)

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

答案 3 :(得分:0)

如果您想利用现代处理器架构,您需要以某种方式在内存中布置数据。 CPU 非常擅长处理在内存中按顺序排列的简单类型。任何其他布局都有更高的处理成本。

在面向对象的方法中,您总是考虑一个实例,然后通过将对象分组到集合中将其扩展到多个实例。但从硬件的角度来看,这会增加成本。

在面向数据的方法中,您没有与面向对象编程中相同的“实例”。您的实例可以有一个标识符,类似于关系数据库中的数据,但除此之外,与您的实例相关的数据可以拆分到多个表中(表实现为向量),以实现高效处理。

一个例子:假设你有 class Student { int id; std::string 名称;浮动平均;布尔毕业; }.在 OOP 的情况下,您会将所有学生放在一个向量中。

在面向数据的设计中,你首先会问自己想要对这些数据做什么样的处理。假设您要计算所有尚未毕业的学生的平均分数。因此,您将创建一个仅包含已毕业学生和另一个尚未毕业的学生的表。您不会在该表中保留学生姓名,因为它不用于处理。但是你会在表格中保留一个学生证和一个平均分。

现在计算未毕业学生的平均分将意味着遍历未毕业表并执行计算。由于平均标记在内存中是相邻的,您的 CPU 将使用 SIMD 并以最有效的方式处理数据。由于我们没有查询 bool 毕业来测试学生是否已经毕业,因此没有数据缓存未命中。

这在理论上听起来不错,但我从未在实际项目中进行过这种开发。如果有人有任何经验,请与我联系,我有很多问题。

答案 4 :(得分:-1)

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

程序方法。

int animation; // this value is the animation index

if(animation == 0)
   PerformMoveForward();
else if(animation == 1)
  PerformMoveBack();
.... // etc

数据设计方法

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();

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