【问题标题】:What is data oriented design?什么是面向数据的设计?
【发布时间】:2010-12-11 03:01:06
【问题描述】:

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

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

【问题讨论】:

标签: data-oriented-design


【解决方案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();

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

【讨论】:

  • 这实际上是不正确的。您将面向数据的设计与数据驱动的设计混淆了。在阅读 Noel 的文章并意识到他说的是完全不同的东西之前,我做了同样的事情。
  • 另外,Indice 不是一个词。有“index”和“indices”之分,有些甚至纵容“index”,但“indice”永远不对。
  • 我使用“dex”作为索引并避免在我的代码中使用复数,因为使事物复数的规则不规则。并且代码应该是常规的。如果您必须打破英语规则以使代码更[统一/常规]这样做。
【解决方案2】:

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

我对面向数据的设计的理解是,它是关于组织数据以进行有效处理。尤其是在缓存未命中等方面。另一方面,数据驱动设计是关于让数据控制程序的许多行为(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();
};

如您所见,不再有单个单位代表一个球。 Ball 对象只隐式存在。

这在性能方面有很多优势。通常,我们希望同时对多个球进行操作。硬件通常需要大量连续的内存块来高效运行。

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

缓存使用示例

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

但它说明了内存布局如何对缓存命中和性能产生严重影响。这只会随着 CPU 和 RAM 速度之间的差异扩大而变得越来越重要。

如何布局内存

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

class Body {
    Point  position;
    double radius;
};

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

    void draw();
};

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

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

与关系数据库的关系

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

【讨论】:

  • 谢谢你,你解释得很好。
  • 说得好;不过我只有一个问题。假设我们有一个结构struct balls {vector&lt;vec3&gt; pos; vector&lt;vec3&gt; velocity;},实际上不会更新每个球的位置,因为你会在速度向量和位置向量之间来回移动(是的,现代机器和缓存线等等) ,这也只是一个插图)?
  • 可能。但请记住,不会一次拉入整个 pos 数组。只有一个缓存行,并且可能进行一些预取。速度也是如此。因此,为了让它们互相丢弃,每个对应的 pos 和 vector 块必须映射到相同的缓存行。这当然会发生,这就是为什么建议将一起使用的变量放在一个结构中的原因。所以例如速度和位置将在一个向量中,而颜色将在另一个向量中。
  • @roe 您应该将一起访问的属性分组在一起。属性之间根本不应该有依赖关系。所以这个结构会更好struct balls { vector&lt;color&gt; colors; vector&lt;body&gt; bodies; /* contains position and velocity */ }.
  • 看起来像是从数组对象切换到数组对象。
【解决方案3】:

我只想指出,Noel 专门谈到了我们在游戏开发中面临的一些特定需求。我想其他正在进行实时软仿真的部门会从中受益,但它不太可能成为一种对一般商业应用程序有显着改进的技术。这种设置是为了确保从底层硬件中挤出最后一点性能。

【讨论】:

  • 同意。面向数据设计的其他一些重要领域包括:用于高带宽设备(例如网络或存储)的硬件和固件;大规模科学计算(例如天气模拟、蛋白质折叠)、信号处理(例如音频、图像、视频)、数据压缩。这些属于“计算科学与工程”,有时作为与更典型的计算机科学分开的专业提供。
【解决方案4】:

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

我对此的基本总结是:如果您想要性能,那么考虑数据流,找到最有可能与您发生冲突的存储层并针对它进行优化困难。 Mike 专注于关于 L2 缓存未命中,因为他是实时的,但我想同样的事情也适用于数据库(磁盘读取)甚至 Web(HTTP 请求)。我认为这是一种进行系统编程的有用方法。

请注意,它并不能免除您对算法和时间复杂性的思考,它只是将您的注意力集中在找出最昂贵的操作类型上,然后您必须使用您的疯狂 CS 技能来瞄准。

【讨论】:

    【解决方案5】:

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

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

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

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

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-03-27
      • 2012-08-21
      • 2011-07-11
      • 1970-01-01
      • 2023-03-20
      • 1970-01-01
      • 2011-02-25
      • 1970-01-01
      相关资源
      最近更新 更多