关键词:平行化和矢量化

http://bbs.9ria.com/thread-400218-1-1.html

虽然说的不是UI组件开发,但是关键词“平行化和矢量化”确是相同的目标

 

=======================================================

你可能已经听说过数据导向的游戏引擎设计,数据导向的游戏引擎设计是一个比较新的概念,其与传统的面向对象设计有很大的不同。在这篇文章中,将解释什么是数据导向设计,为什么有些游戏引擎开发商觉得它可以大大提升性能。


历史


在早期游戏开发,游戏和引擎都是用老旧的语言写,如 C 语言。他们是利基产品,并挤压每个最后时钟周期输出慢的硬件。在大多数情况下, 只有中等数量的人编写代码,他们深知整个代码库。他们使用的工具提供很好的服务,而且 C 语言提供性能优势,可以发挥 CPU 的最高效能,此外这些游戏仍然大量受限于 CPU,绘图到其帧缓冲区,这是非常重要的一点。

随着 GPU 的来临,GPU 可以做三角形,纹理,像素等的数字运算工作,我们得以更少依赖 CPU。与此同时,游戏产业已经稳定增长:越来越多的人想玩更多游戏,反过来驱使更多团队共同开发游戏。

1Moore.png

前天 21:41 上传
下载附件 (7.07 KB)


摩尔定律   显示硬件是指数增长,而非线性成长:这意味着,每隔几年,在一个集成电路板上可以容纳的晶体管数量不是以固定的量增加,而是倍增!


团队越大需要更好的合作。不久前,游戏引擎,用其复杂的水平、人工智能、剔除,并需要编码器更守纪律的渲染逻辑,游戏引擎选择的武器是面向对象的设计


如同 Paul Graham 曾说:

在大公司,软件往往由大的(和经常变化的)平庸的程序员团队​​写的。面向对象的编程强加规定给程序员,以防止太大的伤害。


不管我们喜欢与否,这已是事实,大企业开始部署更大更好的游戏、涌现标准化的工具,编程人员在游戏制作的角色更容易被剔除。特定的编程人员的功能变得越来越不重要。

面向对象设计的问题


面向对象设计是一个很好的概念,它可以帮助开发人员对大项目,如游戏,创建抽象的数层,使每个人针对目标层工作,而不必在意那些底层使我们头痛的实现细节。


我们看到平行编程的爆炸,编码器使所有处理器内核能够传递强烈的计算速度,但在同一时间,游戏的场景变得越来越复杂,如果我们想要跟上这一趋势,也要提供广大玩家期待的 FPS。通过我们手边可达的速度,我们可以展开新的一页:例如使用 CPU 时间,来减少传送到 GPU 的数据数量。


在面向对象编程中,会保持在对象状态中,如果想要多线程工作,需要引进同步原语(synchonization primitives)。为每个虚拟函数调用一个新的间接水平。由面向对象写的内存存取模式。事实上 Mike Acton (Insomniac 游戏,前 Rockstar 游戏)有大量幻灯片解释例子


同样,在卡耐基梅隆大学的教授,Robert Harper

面向对象编程是 ... 其本质是反模块化和反平行,因此不适合现代的 CS 课程。


谈到 OOP 像这样的棘手,因为面向对象囊括了巨大的属性谱,而且不是每个人都同意OOP。我主要讲的是用 C++ 实现的面向对象,因为其是目前主导游戏引擎世界的语言。


所以我们知道,游戏需要成为平行的,因为总有更多 CPU 可以做(但不必须做)的工作,和消费周期等待 GPU 来完成处理仅仅是一种浪费。我们也知道常见的面向对象设计方法要求我们在同一时间引进昂贵的锁,在最意想不到的情况,会违背缓存局部性或造成不必要的分支(其是昂贵的!)。

2Moore.png

前天 21:41 上传
下载附件 (7.59 KB)


如果我们不利用多核的优势,即使有更好的硬件(有更多的内核),而继续使用 CPU 的资源量。与此同时,我们可以把 GPU 推到极限,因为它是平行设计,并能同时进行多任务。如此可以提供玩家硬件上最好的体验。

问题来了:我们应该完全重新思考我们的设计?

入门:数据导向的设计


这种方法的一些支持者称之为数据导向的设计,但事实上其概念已知许久。其基本前提很简单:以数据结构建构代码,并描述要实现的结构操作方面


之前我们已经听过:Linus Torvalds,Linux 和 Git 的创建者,说过 Git 的邮件列表。其为“以数据为主的代码设计,而不是以其他为主”的巨大支持者,并造就了Git 的成功。甚至他声称,一个好的程序员和一个坏的区别是,她是否担心数据结构或代码本身。


任务似乎违反直觉,因为它需要把心智模式颠倒。用这样的方式思考:当运行一个游戏时,获取所有用户的输入,以及所有表现重块(符合一切都是对象的哲学)不依赖外部因素,如网络或 IPC。对于你所知道的,玩家事件(移动鼠标,按下操纵杆按钮等)和当前的游戏状态,并这些组成新的数据,例如送到 GPU 的数据、批次送到声卡的 PCM 样本,以及新的游戏状态。


此“数据搅动”可以被细分为更多的子流程。动画系统使用下一个关键帧数据和目前帧,来产生一个新的状态。粒子系采用当前状态(粒子位置,速度等)和时间的推进,来产生一个新的状态。剔除算法采用一组可渲染的候选来产生更小的可渲染集合。几乎在游戏引擎的一切都可以被认为是操纵数据块来产生另一个数据块。


处理器爱用局部参考和利用高速缓存。因此,在数据导向的设计中,我们往往会尽可能安排大的同质数组,而且尽可能良好运行,使用高速缓存穷举算法代替潜在空想方法(其可能有更大的 O 成本,但由于架构的限制,所以无法适用于硬件)。 

当每帧(或每帧多次)执行时,就可能有巨大的效能回馈。例如,Scalyr 报告使用精心制作的,异想天开的强力线性扫描在 20GB/sec 的速度下,搜索日志文件

3Moore.png

前天 21:41 上传
下载附件 (8.44 KB)


当我们处理对象时,要视之为“黑箱”,并调用它们的方法,从而访问想要存取的数据。这样会有很大的工作可维护性,但并不知道数据是如何处理,而造就这样的性能。


范例


数据导向的设计终日所想尽是数据,让我们做一些有别于平常的事情。 试想这段代码:

  1. void MyEngine::queueRenderables()
  2. {
  3.   for (auto it = mRenderables.begin(); it != mRenderables.end(); ++it) {
  4.     if ((*it)->isVisible()) {
  5.        queueRenderable(*it);
  6.   }
  7. }
复制代码

虽然简化了很多,这种常见的模式经常出现在面向对象的游戏引擎。且慢,如果有很多可渲染的实际上不是可见的,我们会遭遇很多的分支错误预测,因而导致处理器破坏一些希望特定分支执行的指令。


对于小场景,这问题不明显。但是,你做过多少次这样的特别事情?不只是在排队可渲染的东西时,也在场景灯光,阴影贴图分割,区域等时?AI 或动画更新时会怎样呢?整个场景的倍数乘积,看你经历过多少个时钟周期,计算出处理器在稳定的 120FPS 节奏下可提供批次数据给 GPU 要花多少时间,看到这些用想的也知道是很可观的数量。


这将很有趣,例如,一个编程人员在网页应用程序做了微乎其微的微观优化,但我们知道,实时游戏系统中的资源很吃紧,所以这样的优化不无小补。


为了避免这种情况发生,让我们想想另一种方法:如果我们使引擎持有可见的可渲染的名单如何?当然,我们会牺牲整齐语法 myRenerable ->hide() 并违反了不少面向对象的原则,但随后我们可以这样做:

  1. void MyEngine::queueRenderables()
  2. {
  3.   for (auto it = mVisibleRenderables.begin(); it != mVisibleRenderables.end(); ++it) {
  4.     queueRenderable(*it);
  5.   }
  6. }
复制代码

万岁!没有分支错误预测,并假设 mVisibleRenderables 是​很好的 std::vector (连续的数组),我们也可以重写此为快速的 memcpy 调用(我们的数据结构可能有一些额外的更新)。


现在,你可以用此简化很多代码。但说实话 ,这只是冰山一角。考虑数据结构和它们之间的关系赋予我们许多以前没有想过的可能性。让我们来看看其中的一些未来。


平行化和矢量化


如果我们有一个简单的、明确功能的、大型的、作为进程基础的数据块,可以很容易分成 4 个、8 个,或16 个工作线程,并给每个内核一块数据,以确保所有的 CPU 内核有事做。没有互斥体 ,原子能或锁争用,一旦你需要数据,只要把所有的线程加在一起,并等待它们完成。如果要平行排序数据(准备送到 GPU 时,非常常见的任务),只要从另一个角度思考,这些幻灯片可能有帮助


附加好处,在一个线程里面可以使用 SIMD 向量指令(如 SSE/SSE2/SSE3 )来实现额外的速度提升。有时候,只要用以不同的方式呈现数据,如以数组结构的方式 (SOA) 来放置矢量数组(如 XXX ...YYY ...ZZZ ......), 而不是传统的结构数组 (AOS,像是 XYZXYZXYZ ...)。我勉强揭开冰山;你可以在下方进一步阅读找到更多的信息。

4Moore.png

前天 21:41 上传
下载附件 (7.19 KB)


当我们的算法直接处理数据,平行化就变成微不足道,我们也可避免一些速度的缺点。

单元测试(你可能不知道)


有简单的功能,无需外部效应很容易进行单元测试。这在想容易替换进出的回归测试的算法特别好。

例如,你可以建立一个测试套件,来进行剔除算法的行为,建立一个协调的环境,并测量它究竟是如何执行。当设计一个新的剔除算法,不用改变什么就可以做相同的测试。可以轻而易举测量效能和正确性。 

当越深入数据导向的设计方法,会发现它越容易测试游戏引擎。


结合类对象和单片数据


数据导向的设计绝不是反对面向对象编程,其只是一些概念。因此,你可以很巧妙地利用数据导向的设计思路,仍然可以使用已经习惯的大多数的抽象和心智模式。

一起来看看,例如,在   OGRE 版本 2.0 的工作 :Matias ·Goldberg,努力的幕后主谋,选择用大的、同构型数组存储数据,为了加速 Orge,因此有遍历整个数组的功能。根据标杆(他承认很不公平,不能只凭此判断性能优势),它的表现快三倍。不仅如此,他保留了很多旧的,熟悉的抽象类,而不是 API 完全重写。


是否切合实际?


有很多证据显示,这种方式的游戏引擎能够做出来而且将会发展。

粒子引擎的开发博客有一系列的名为在数据导向设计的冒险 , 包涵有关于数据导向设计的很多有用的建议。

DICE 似乎对数据导向设计感兴趣,因为他们雇用了 Forstbite 引擎的剔除系统(也获得显著加速!)。其他的幻灯片还包括在 AI 子系统采用数据导向设计 ,也值得看。

此外,开发人员如上述的 Mike Acton 似乎接受这个概念。有几个标杆测试,证明它确实有很好的性能,但我还没有看到很多数据导向设计的活动,还有很长一段路要走。当然可能只是一时流行,但其主要概念似乎非常合乎逻辑。在此商业确实有很多惰性(对于这个问题,其他任何软件开发商业也是),所以大规模采用这种理念可能会有阻碍。或者其不如所想的那么好。你说呢?欢迎评论!

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值