首先,不要将此与数据驱动设计混淆。
我对面向数据的设计的理解是,它是关于组织数据以进行有效处理。尤其是在缓存未命中等方面。另一方面,数据驱动设计是关于让数据控制程序的许多行为(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 缓存,而是内存中的页面。一个优秀的数据库设计人员也可能会将不经常访问的数据拆分到一个单独的表中,而不是创建一个包含大量列的表,而其中只有少数列被使用过。他还可能选择对某些表进行非规范化,以便不必从磁盘上的多个位置访问数据。就像面向数据的设计一样,这些选择是通过查看数据访问模式是什么以及性能瓶颈在哪里来做出的。