wolf96翻译,有不正确或者要补充的地方欢迎评论指正。
对于开发末期的循环,你的游戏正在缓缓开发,但是你没有看到任何热点,原因?随机记忆使用模式和固定的存储器的缺失。企图改进性能,你尝试这比较你的部分代码,但是,并且在最后,由于同时你必须要添加所有的的你仅仅得到了速度加快。To top it off, 代码太复杂了以至于固定的bugs产生了更多的问题,并且增加新特色的想法也放弃了。
这件事实在是准确的描述了在这十年我遇到的几乎所有游戏中的情况,问题不在于我们用的编程语言,也不是编程工具,也不是缺乏训练。在我的经验看来,这是object- oriented programming (OOP) 面向对象编程,和很大部分归咎于这个问题包围着的编程文化。OOP会阻碍你的程序而不是帮助。
这全都与数据有关
OOP已经在现在的游戏开发当中根深蒂固了以至于当我们想游戏时很难超越object,毕竟,我们已经构建了好多年的类作为媒介(vehicles),玩家(players),和状态机(state machines),什么能替代它?程序设计?功能语言?外来的编程语言是?
面向数据设计(Data-oriented design )是一种不同的程序设计解决(addresses)所有的这些问题,程序设计是程序的重要元素,并且由OOP解决目标的主要问题。注意到主要的方法在于编码:某一种情况的简单的程序(或函数),并且为代码分组根据一些它们的内在规定使它们联接,面向数据设计(Data-oriented design )改变了从目标(objects)到数据本身的看法(perspective)。数据的类型,怎样放置在存储记忆中,它将怎样在游戏中被读出和处理介绍
程序设计,从定义来讲,是关于改变数据的,它致力于创造一个序列的指令来表示怎样处理输入数据和一些特殊的输出数据。游戏不过是仪的实时工作的程序,所以他不会对我们着重于数据而不是操做它的代码产生感觉不是吗?
我想清楚关于面向数据设计(Data-oriented design )的误解和压力不意味着一些数据驱动的东西。一个数据驱动游戏经常是暴漏了大量的功能(functionality )在代码外并且让数据决定游戏中的行为。这与面向数据设计(Data-oriented design )彼此正交,并且能应用在很多种类型的设计方法。
理想的数据
如果我们从数据的观点看一个程序,理想的数据看起来像什么呢?这取决于数据和怎样用它。一般来说,理想的数据就是在一种格式中我们能用最小限度的努力。在最好的情况下,格式将和我们期望的输出一样,所以这个处理收到只,是复制数据的限制。经常地,我们的理想数据设计将是大块的连续,我们能连续处理同种数据。在任何情况,目标是使转换(transformations )的数量最小,并且在可以的情况下,你需要在这个理想的格式在离线中备份你的数据(bake your data ),在你处理资产建设时。
因为面向数据设计(Data-oriented design )把数据作为首位重要的,我们能围绕着理想数据格式构建整个程序。我们不能总是把它做得非常理想(在同样的方式代码几乎不会按照OOP的惯例),但是这是记住它的主要目的。我们实现了一次,我开始提到的所有问题都消失了。
当我们想到面向对象时,我们立即会想到树,继承树,牵制树,或者消息传递树,我们的数据能以那种方式自然的安排。结果,当我们在一个对象上执行操作时,它将经常导致在一个对象在树中转而访问另一个对象。迭代一组对象执行一样的操作产生的效果,完全不同的运作在每个对象中。(见图1a)
图2 面向数据方法的执行顺序
尽可能地实现最好的程序设计,这对在每一个对象中的不同组件解决故障有帮助,在存储中一批相同类型的组件在一起,不管对象是从哪来的。这时大量的同种数据的组织结果,让我们循序的处理数据(见图1b),面向数据设计(Data-oriented design )之所以非常强大的原因在于它在大批的对象中能工作的很好。OOP,按照定义,工作在单个对象上。后退一分钟想一下你最后一次做的游戏:在代码中多少个地方你只做了一个东西?一个敌人?一辆车?一个寻路节点?一个子弹?一个颗粒?从来没有!有一个,就有很多。OOP忽视对象单独的处理。代替,我们能让事情更简单并且对于硬件也是,组织我们的数据去处理许多项目的的相同情况的同种类型。
这个听起来像奇怪的方法?猜猜看?你可能准备在你的代码里用一些这种方法:粒子系统!面向数据设计(Data-oriented design )把我们所有的代码库变成一个巨大的粒子系统。也许粒子驱动编程这个方法的名字将要在游戏程序员中更加常见。
面向数据设计(Data-oriented design )的优势
并行化
这些天,没有方法处理的多核的问题,所有人都想尝试着用OOP代码,但事实证明实在是太难了,容易产生误差,而且没有效率。你经常在多线程中以增加很多的同步性基元去阻止并行存取数据来结束(end up),并且经常有大量的线程有一段时间的空闲来等待其他线程结束。结果,性能改进的效果很差。
当我们运用 面向数据设计(Data-oriented design )时,并行化将变得非常简单:我们有输入数据,一个函数来处理它,和一些输出数据。我们能简单地让一些东西在他们之间以最小的同步在多线程中分离。我们甚至能更进一步考虑并且用本地内存在处理器上执行代码(就像在cell处理器上的SPUs)
缓存使用情况
除了运用多核之外,一个实在现代的硬件现良好性能的关键是,深入其指令流水线使多级缓存的存储系统变缓慢,这能带来友好的存储访问。面向数据设计(Data-oriented design )使之高效利用指令缓存,因为相同的代码反复执行。另外,如果我们运用大量的数据,能在相邻的块中连续的处理数据,得到近乎完美的缓存利用和良好的性能。
尽可能的优化。当我们在想对象或者函数时,我们趋向在函数或者甚至是重写了一些c语言的代码放在里面来优化。
这种优化当然是有益的,但是如果我们通过思考数据第一能进一步的回顾或者再加一些东西,获得更重要的优化。记住一个游戏的全部做的是转化一些数据(资源,输入,状态)到另一些数据(图形命令,新的游戏状态)。通过记住这个数据流,我们能让它更高级,决策更智能基于数据怎样转化,怎样被使用。这种优化能非常不同并且消耗时间来执行更多的传统OOP的方法。
模块性
迄今为止,所有的面向数据设计(Data-oriented design )的优势都是性能优势:提升缓存利用率,优化和并行化。对于作为游戏程序猿的我们来说改善性能毫无疑问是一个非常重要的目标。这经常是一个在用方法提高性能和技术来提高可读性和易于开发之间的矛盾。举个例子,用汇编语言重写一些代码能使性能提升,但是经常导致代码难以读懂难以维护
幸运的是,面向数据设计(Data-oriented design )是对于开发和性能都有益的。当你再写一些改变(transform )数据的特殊的代码时,你可以写一些小函数很少相关于其他部分的代码。代码非常平整的结束,拥有大量的叶子函数(如果一个函数不再调用其他函数,这样的函数被称为叶子函数 leaf functions )没有很多相关性。这个级别的模块性没有很多相关性通俗易懂,替换更新代码变得容易。
测试
面向数据设计(Data-oriented design )最后的优点是便于测试。我们看到了七月和八月的内部产品数据,基础单元测试去检测对象的交互性是琐碎的。你需要建立模拟并且间接地测试。坦白地讲,这有一点痛苦。另一方面,当直接处理数据时,编写单元测试(单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证)并不简单:创建一些输入数据。调用一些转换(transform )函数,并且检查输出数据是否是我们期待的。不论你在做驱动的测试还是编写单元测试这真的带来很大的好处并且让代码的测试非常简单。
面向数据设计(Data-oriented design )的缺点
面向数据设计(Data-oriented design )不能解决所有游戏开发中的问题。它只能帮你写出非常高效的代码并且让代码更易读更易于维护,但是它确实也有一些自己的缺点。
面向数据设计(Data-oriented design )最主要的问题在于它与大多数程序猿使用的或在学校学习的用法有所不同。它需要把我们心里的标准样式转变九十度并且改变我们对它的看法。它让之前的练习成为老习惯。
并且,因为它是一种不同的方法,使它的现有代码表面具有挑战性,代码方式更加的OOP。在单独的环境写一个单独的函数很难,但是只要你能使用面向数据设计(Data-oriented design )在整个子系统你能获得很多好处。
使用面向数据设计(Data-oriented design )
有了足够的理论知识。怎样才能实际的开始使用面向数据设计(Data-oriented design )?首先,在你的代码中选择一个特定区域:导航,动画,碰撞,或者其他东西。然后,当你的游戏引擎大部分都以数据为中心时,你就能考虑数据流从开始的框架(frame)一直到最后。
下一步是去清楚地确定被系统需要的输入数据,什么类型的数据需要生成。现在可以从OOP的角度考虑,只是帮助我们确定数据。举个例子,在一个动画系统中,一些输入数据包括,骨架,基本姿势,动画数据和当前状态。结果是“代码使动画播放”,但是动画生成的数据正在播放。在这种情况,我们的将要输出的数据是一个新的姿势和一个更新的状态。
再进一步,根据数据会被怎样使用对输入数据进行分类是很重要的。它是只读,可读写,或者只写?这种分类将对决定在哪里存储它有帮助,并且根据它和其他部分的相关性来处理它。
这时候,停止思考一个简单的操作的数据需要,并且打算将它应用到几十或几百个项上。我们不再有一个框架,一个基本姿势,和一个当前状态,取而代之,在每一个块中的许多实例中,我们有一大块这样的类型。
谨慎的考虑在转换处理中从输入到输出数据是怎么用的。你也许认识到你需要在一个结构去执行一个数据的通过中扫描一个特别的域,并且当你需要用另一个通过的结果。在这种情况,他也许能在初始域到一个单独的存储块中能做出合理的划分来独自处理,考虑到更好地利用缓存与可能的并行化。或者也许你需要矢量化一些代码,哪需要从不同地点读取数据就把它放在相同的寄存器中。在这种情况下,数据能连续存储,所以矢量操作能不需要任何额外转换来直接提供。
现在你需要对数据有一个很好的认识。写代码来转换它将变得更加简单。就像填空题一样。与相同条件下的OOP代码相比较,你甚至将惊喜的发现代码特别简单而且比以前的要小。
如果你去想以前最后一年中关于们在本栏中覆盖的大多数的主题,你将会看到他们以前全部是主要用这种类型的设计。现在是时候去对怎样对齐数据小心考虑了(2008.12-2009.1),在你能有效的利用一个输入格式直接烘焙数据(2008.10-2008.12),或者在数据块之间用非指针的引用,所以他们能被简单的重置。
还有OOP的余地了吗?
这意味着OOP的用处少了并且不使用吗?并不是这样。当每个对象只有一个时它并不是坏东西。(一个图形设备,一个日志管理等)虽然在这种情况下也许还是写一个c类型的函数更好和文件级的静态数据。即使在这种情况下,在在被设计得围绕着变化的数据中也是重要对象。
在GUI系统中下我也用OOP。因为你用一个系统工作也许它已经被设计成一个面向对象的方式,或者也许因为它的性能和复杂性在GUI代码中不是关键因素。无论如何,我特别喜欢GUI的API尽可能的用在继承和控制(Cocoa(Cocoa是苹果公司为Mac OS X所创建的原生面向对象的API)和CocoaTouch(Cocoa Touch由苹果公司提供的软件开发API, 用于开发 iPhone\iPod\iPad上的软件。也是苹果公司针对iPhone应用程序快速开发提供的一个类库)是很好的例子)。这非常可能用面向数据GUI系统,能在游戏中被写入,将会很好的工作,但是我没见到过。
最后,没有什么能阻止你从关于对象的的思考。如果这是你思考游戏的方式。这只是敌方实体,不能成为在内存中在相同的物理位置。取而代之,它将分成最小的子组件,在类似的组件中每一个形成一个大数据表的一部分。
面向数据设计(Data-oriented design )是违背传统编程的方法之一,但是经常思考关于数据和需要怎样被转化,你将能获得巨大的好处无论是性能还是简化开发。
原文链接 :http://gamesfromwithin.com/data-oriented-design