微信号:weixin888
P社全家,钢铁雄心啊,群星什么的,rimworld环世界,石炉,监狱设计师等等... 我发现在玩这些游戏时候,都是一核有难 多核围观
你能问这问题,说明你不了解并行优化是怎么回事。
很难。但是并不是你想象中的完全没有优化。事实上绝大部分游戏都做了这样几种优化,1,图形渲染线程与逻辑线程并行。(没有办法完全并行)2,IO线程与其他线程并行(存档与网络通信。少数没做这个优化的,可能是因为实时性非常重要
但是即使做了这些,大部分情况下还是一核有难多核围观的结果。
大部分是卡顿的原因逻辑线程太繁忙了。比如监狱设计师这种游戏,一个逻辑周期里面可能要检查几千几万个对象的状态,其中几千个可能存在寻路,碰撞,交互,随机事件等。监狱设计师的实际体验来看,一秒钟不会少于20个逻辑周期。
这些逻辑是严格实时,强交互的,根本没有一种通用的框架可以并行化。
并行化的方法当然会有,但是得针对实际逻辑单独开发,对大多数游戏厂商来说都负担不起这样的时间成本和开发成本。
很难。
举个不是很恰当地例子吧,假如有1个小朋友来做5道题:
1+2=A
A+5=B
B+10=C
C+2=D
D-4=E
需要计算E是多少,那么,以下情况,哪个速度快?(假设每个小朋友计算速度一样快)
甲:1个小朋友按顺序做下来
乙:5个小朋友同时做,每个小朋友做一题
乙方案,在第一个小朋友做出来之前,第二个小朋友只能等着(因为他不知道A是多少),同理,其他小朋友也是如此,所以效率并没有明显的提高,所以这种情况下,很难优化。
这种情况就是大部分情况下逻辑线程的执行情况。
-----------分割线----------
假如是做这样的题:
1+2=A
3+5=B
8+10=C
18+2=D
20-4=E
很明显,5个小朋友同时做会比一个小朋友做的快的多,这样是可以优化的。
这种情况就是逻辑线程、IO、渲染UI、网络请求等可以异步的操作,大部分游戏/应用都会做这种优化。
----------
举得例子可能不太恰当,理解就好。
多线程/并行化就是难,而多线程最大的难点在于线程间同步/互斥。
比如P社游戏,看起来所有AI的行动都是独立的可以并行,但有个关键问题是,AI的行动需要“更新地图信息”,而每一次行动以后地图都会改变,而这种改变一定会影响其他AI的决策,所以同一个AI的行动可能会有多线程优化的空间,然而不同AI的行动必须是按顺序一个一个来的,所以这把大锁就让P社游戏的多线程优化增加了很大的困难。
P社想要利用多核,比较好的办法是修改游戏机制使其能够允许同时/同步行动。。。然而好好的一个4X怎么能变成rts呢对不对。。。
然而你以为rts多线程就好做了么,当然你一个AI可以一个线程的,然而地图还是全局,还是有锁 。。。而且。。。你造为啥rts有人口限制吧233。
上面只是用P社举个例子,然而不幸的是这个例子对很多游戏都适用:“全局游戏数据”。
很多运算都可以并行,但是对全局游戏数据的修改必须加锁,这个锁你可以费很大力气分成若干把小锁,但是这样的话你又要用很多力气去保证游戏数据的一致性。。。单机游戏有一个好处是CAP定理中的分区容错性不需要考虑,所以多核优化还是可能的,但是最终的性能瓶颈还是会出现在对“全局游戏数据”的读写上。。。而且你总不能为了写个游戏造个dbms出来对吧。
真正能够实现并行的游戏都是在设计上就考虑了并行性的,然而这对设计就提出了很神奇的要求。。。当然游戏行业本来就是戴着性能的镣铐跳舞的。
另外d12的那个“多核心优化”说的是渲染线程,和逻辑线程没啥关系,反而更挤占了逻辑线程能用的核心数。
btw,你拿fps跟4x比你不觉得这对4x不太公平么。fps的逻辑本来就少啊,重点都在物理引擎和渲染上。
由于题主似乎不是太了解游戏开发的编程方面,以下我只是简单概括的谈一下。
游戏在某方面来说,可以是相当复杂的软件。一个游戏可能涉及许多系统/子系统共同运作,例如常见的有游戏性、图形、音频、输入、动画、物理、人工智能、资源管理、内存管理、数学库等等。这些系统会彼此依赖,例如游戏性系统想要知道玩家角色的头部位置,需要从动画系统取得,而动画系统也要知道当时角色的动作状态,这个状态又依赖于游戏逻辑。如果要用多核并行处理一些任务,需要考虑任务之间的依赖性。并行时需要任务之间不互相依赖。如果游戏性本身需要并行,要考虑所有依赖性问题,然后用某种框架来编写游戏逻辑。另一解决方法之一,有时候会使用异步方式去处理,例如在这一个时步做射线检测,发起请求后,我们可能在下一个时步才获取结果。缺点除了增加了延时,也会令程序变得复杂。
大部分游戏引擎可以透明地做一些并行优化,例如会把动画、物理、渲染等子系统在独立线程或分拆成多任务去执行,令游戏性(通常游戏开发者要做的部分)的开发不需考虑并行问题。而一般来说,游戏性部分也通常不是瓶颈(也许一些RTS类型等多游戏对象的问题较大)。所谓具体问题具体解决,不同游戏遇到「卡顿」、「帧率低」的不流畅体验,需要看问题所在才能优化。例如「卡顿」有可能是一些同步I/O或GC做成的问题,需要针对性进行优化。「帧率低」比较常见是 GPU 的瓶颈,或是 draw-call 太多,都要用不同方法去缓解。如果问题不在 CPU,并行化并不会有什么帮助。另外,如果问题在于内存带宽不足或缓存命中失败,多核是解决不了问题的,反而有可能令整体性能更差。
Windows 10 和游戏中的多线程优化是没什么关系的。说一下 PC 游戏的开发困难。相比游戏机,PC的配置差异很大,核心数量不同,要优化到在不同配置下都有高 CPU 利用率会更困难。GPU就更不用说,高配和低配显卡性能可以相差两个数量级。也有可能一些硬盘慢,串流速度赶不上,出现需要等待 I/O 的情况。相对来说,游戏机的配置比较固定,优化目标清晰,所以一般也能表现得比同等配置的 PC 版好。
关于游戏开发的并行问题,可参考[1]的 §7.6(多处理器的游戏循环)和§14.6.4(为并行设计)。
[1]Jason Gregory著,叶劲峰译,《游戏引擎架构》,电子工业出版社,2014。
我来尝试一一解答,你的问题因为篇幅我稍作删减处理:
1:疑惑:我在玩很多游戏的时候,发现游戏本身对GPU占用不是很高,很多都是依赖CPU进行处理,但是这些游戏对CPU利用率非常的低(通常都是单线程)。
题主你提到的游戏我虽然都没玩过(因为工作原因现在很少有时间玩游戏),但我还是搜索了一下你说的这几个游戏,她们的画面是这样的(YouTube截图):
看到这里相信已经很明显为什么她们的GPU占用率很低了:因为绘制简单。其实CPU和GPU本质上都是获取指令,获取数据,执行指令计算,存储计算结果,画面简单自然计算量低,计算量低以现代的GPU瞬间就算完了,自然占用率低。
AAA大作里通常都是相反的,一般都是GPU是瓶颈。主要原因有两点,其一是图形复杂,其二是其实现在的GPU因为其优秀的并行特性还承载了很多非图形的计算工作,比较流行的大概有在compute shader里做模型的skinning,布料动态模拟,甚至一些AI逻辑。
2:提问:为什么有些厂商在做游戏时候不做多线程/多核优化?这是一件很困难的事情么?
恰恰相反,现在CPU的多线程并行计算在游戏引擎里已经非常普遍了,区别不在于有没有,而在于各团队做到了一个什么程度。
在这里无意展开,大概说一下游戏引擎里比较普遍的可以并行去做的系统:比如动画计算,物理引擎中有很大一部分可以并行,碰撞测试,粒子特效,等等等等。
至于难不难么,我认为要分两方面去看:
2.1:多线程系统的实现。这属于引擎底层核心,包括多线程的创建,管理,调度,debug等等方面。这一块必须必须由经验丰富,基础扎实,对自家引擎熟悉的老兵来把握,难度极高,新人做不来。但值得一提的是这种系统开发成本虽然高,但开发完成之后可以持续使用一两个世代。
2.2:多线程系统的应用。底层开发好之后,上层使用就简单多了,比如gameplay程序需要把游戏逻辑切成一块一块可以并行的小块,然后调用底层系统执行。说实话如果底层系统对使用者友善的话,应用起来不难,需要一段时间适应并行思维,但习惯了就好。
3:提问:如果要做多线程/多核优化,应当是怎么样的一种思路?
这个没有固定的方法,也没有绝对的对错,总的原则就是,慢了就想办法优化,从容易优化的地方入手,够用就好。
从我使用过的3个AAA引擎来看,大概有两种思路吧:
3.1:Crystal Dynamics和Sledgehammer Games。一个是古墓丽影的引擎,一个是使命召唤的引擎,这两个引擎的实现是,把比较明显的,容易并行的地方做并行,剩下的在一个主线程内继续单线程运行。
3.2:顽皮狗。顽皮狗的引擎在并行优化上做得是我见过最极致的引擎,使用的是比线程更轻量级的fiber,细节不展开说,主要思路是多核从一开始就并行,基本上不再存在主线程这个概念。当然了,因为计算任务和任务之间仍然存在依赖性因此等待是无法完全避免的,但并行程度一般来说比我接触过的其它引擎更好(还有更高级的多帧在时间上重叠也不展开说)。
这里给出一个顽皮狗引擎并行化的讲座链接,是我们其中一个技术总监讲的,内容很优秀:
Parallelizing the Naughty Dog Engine Using Fibers
4:提问:win10这部分(多线程)内容优化在哪里?
win10是操作系统,操作系统自身有任务调度和安排的功能,但跟前面说的引擎内并行优化是两码事,有兴趣你可以网上找找操作系统的入门知识介绍,这一块内容很深。
5:总结。
并行这东西说白了就是具体情况具体处理,因为大家的引擎架构,实现,计算需求都不同。如果有一个相对通用的底层系统当然很好,没有的话各厂商也把东西硬做出来了。并行是有代价的,所以计算任务的粒度(granularity)要把握好,否则可能得不偿失,最终目标是取得动态平衡,把你想要的效果在你想要的效率里实现出来。