我应该有多less个线程?为了什么?

我应该有单独的线程渲染和逻辑,甚至更多?

我意识到由数据同步引起的巨大性能下降(更不用说任何互斥锁)。

我一直在想这个到极点,为每个可以想象得到的子系统做线程。 但我担心可能会减慢速度。 (例如,将input线程与渲染线程或游戏逻辑线程分离是否理智?)所需的数据同步是否使其毫无意义,甚至更慢?

坦率地说,利用多核心的通用方法只是简单的误导。 把你的子系统分成不同的线程确实会把一些工作分解成多个内核,但是它有一些主要的问题。 首先,很难合作。 谁想要lockinglocking,同步和通信,以及什么时候可以直接写渲染或物理代码? 其次,这个方法实际上并没有放大。 充其量,这可以让你利用三到四个核心,这就是如果你真的知道你在做什么。 游戏中只有很多子系统,而那些占用大量CPU时间的子系统则更less。 我知道有几个很好的select。

一个是每个附加的CPU都有一个主线程和一个工作线程。 无论子系统如何,主线程都通过某种队列将独立的任务委托给工作线程。 这些任务本身也可以创build其他任务。 工作线程的唯一目的是每次从队列中抓取任务并执行它们。 但最重要的是,只要一个线程需要一个任务的结果,如果任务完成,它可以得到结果,如果没有,它可以安全地从队列中删除任务,并继续执行任务本身。 也就是说,并不是所有的任务最终都会被并行安排。 在这种情况下,有更多的任务可以并行执行是件好事; 这意味着它可能会随着您添加更多内核而扩展。 这样做的一个缺点就是需要大量的工作来devise一个体面的队列和工作循环,除非你可以访问已经为你提供的库或者语言运行库。 最难的部分是确保你的任务是真正的孤立和线程安全的,并确保你的任务是在粗粒和细粒之间的快乐中间地带。

子系统线程的另一种替代方法是将每个子系统隔离并行。 也就是说,不是在自己的线程中运行渲染和物理,而是写物理子系统来同时使用所有内核,编写渲染子系统以便一次使用所有内核,然后让这两个系统按顺序运行(或交错,取决于你的游戏架构的其他方面)。 例如,在物理子系统中,你可以把游戏中所有的点群分开,把它们分在你的核心之间,然后让所有的核心立即更新它们。 然后,每个核心都可以在紧密的循环中处理数据,并具有良好的局部性。 这种锁步式的并行性与GPU所做的相似。 这里最难的部分是确保将工作划分为细粒度的块,以便均匀地分配, 实际上可以在所有处理器上实现相同的工作量。

然而,由于政治,现有代码或其他令人沮丧的情况,有时候最简单的办法是给每个子系统一个线程。 在这种情况下,最好是避免为CPU繁重的工作负载创build比内核更多的操作系统线程(如果您的运行时轻量级线程恰好在您的内核之间平衡,这并不是什么大问题)。 另外,避免过度的沟通。 一个好的窍门是尝试stream水线; 每个主要子系统可以一次处于不同的游戏状态。 stream水线操作减less了子系统之间所需的通信量,因为它们不需要同时访问相同的数据,也可以消除由瓶颈造成的一些损害。 例如,如果您的物理子系统往往需要很长时间才能完成,并且您的渲染子系统总是等待它,那么如果在渲染子系统仍在工作时为下一帧运行物理子系统,则绝对帧速率可能会更高在前一帧。 事实上,如果你有这样的瓶颈,不能以任何其他方式去除它们,stream水线可能是打扰子系统线程的最合理的理由。

有几件事情要考虑。 线程每子系统的路线很容易思考,因为代码分离是非常明显的。 但是,根据您的子系统需要多less互通,线程间通信可能真的会导致您的性能下降。 另外,这只能扩展到N个核心,其中N是你抽象成线程的子系统的数量。

如果你只是想multithreading化现有的游戏,这可能是阻力最小的path。 但是,如果你正在研究一些可能在几个游戏或项目之间共享的低级引擎系统,那么我会考虑另一种方法。

这可能需要一点思考,但是如果你能把一些工作线程分解成一个工作队列,那么从长远来看,这个工作将会变得更好。 随着最新和最伟大的芯片出现一个巨大的内核,你的游戏的性能将随着它的扩展,只是激发更多的工作线程。

所以基本上,如果你想要对现有项目进行一些并行处理,我会跨子系统进行并行处理。 如果你想从头开始构build一个新的引擎,并考虑到并行扩展性,我会考虑一个工作队列。

这个问题没有最好的答案,因为这取决于你想要完成什么。

Xbox有三个内核,可以在上下文切换开销成为问题之前处理几个线程。 电脑可以处理更多。

为了便于编程,很多游戏通常都是单线程的。 这对大多数个人游戏来说都很好。 networking和audio,你可能必须有另一个线程。

虚幻有一个游戏线程,渲染线程,networking线程和audio线程(如果我没有记错的话)。 这对于很多当代引擎来说是非常标准的,尽pipe能够支持单独的渲染线程可能是一个痛苦,并且涉及很多基础工作。

为Rage开发的idTech5引擎实际上使用了任意数量的线程,并且通过将游戏任务分解为由任务系统处理的“工作”来实现。 他们明确的目标是当平均游戏系统的核心数量跳跃时,他们的游戏引擎可以很好地扩展。

我使用的技术(已经写入)具有用于networking,input,audio,渲染和调度的独立线程。 然后它有任何数量的线程可以用来执行游戏任务,这是由调度线程pipe理。 很多工作都是为了让所有的线程能够很好地相互协作,但是它似乎运行良好,并且得到了非常好的多核系统的使用,所以也许它完成了任务(现在我可能会分解audio/networking/将工作input到工作线程可以更新的“任务”中)。

这真的取决于你的最终目标。

每个子系统的线程是错误的路线。 突然之间,你的应用程序将不会扩展,因为一些子系统比其他子系统需求更多。 这是Supreme Commander采用的线程化方法,并没有超出两个内核,因为它们只有两个子系统占用大量CPU渲染和物理/游戏逻辑,即使它们有16个线程,其他线程只是几乎没有任何工作,因此,游戏只能扩展到两个核心。

你应该做的是使用一个叫做线程池的东西。 这有点反映了在GPU上采取的方法 – 也就是说,您发布了工作,任何可用的线程都会随之而来,然后返回到等待工作的状态 – 想象一下线程的环形缓冲区。 这种方法具有N核扩展的优势,并且在低核心和高核心计数方面都非常出色。 缺点是这种方法的线程所有权非常困难,因为在任何时候都不可能知道哪个线程在做什么工作,所以你必须把所有权问题locking得非常紧密。 这也使得使用不支持multithreading的Direct3D9等技术变得非常困难。

线程池非常难以使用,但它们提供了最好的结果。 如果您需要非常好的缩放比例,或者您有足够的时间来处理它,请使用线程池。 如果您试图将并行性引入到具有未知依赖性问题和单线程技术的现有项目中,则这不是您的解决scheme。

你是对的,最关键的部分是尽可能地避免同步。 有几种方法来实现这一点。

  1. 了解您的数据并根据您的处理需求将其存储在内存中。 这使您可以计划并行计算,而不需要同步。 不幸的是,这大部分时间是很难实现的,因为数据经常在不可预知的时间从不同的系统访问。

  2. 定义明确的数据访问时间。 你可以把你的main-tick分成x个阶段。 如果您确定线程X仅在特定阶段读取数据,则您也知道此数据可以由其他线程以不同阶段进行修改。

  3. 双缓冲您的数据。 这是最简单的方法,但它增加了延迟,因为线程X正在处理来自最后一帧的数据,而线程Y正在准备下一帧的数据。

我个人的经验表明,细粒度计算是最有效的方法,因为这些方法可以比基于子系统的解决scheme更好地扩展。 如果你的子系统是线程的,那么帧时间就会被绑定到最昂贵的子系统上。 这可以导致所有的线程,只有一个空闲,直到昂贵的子系统终于完成它的工作。 如果您能够将大部分游戏分成小任务,则可以相应地调度这些任务以避免闲置内核。 但是如果你已经有了一个很大的代码库,这是很难完成的。

考虑到一些硬件限制,你应该尽量不要超额订阅你的硬件。 对于超额订阅,我的意思是有比您的平台硬件线程更多的软件线程。 特别是在PPC体系结构(Xbox360,PS3)上,任务切换真的很贵。 当然,如果你有一些超额订阅的线程,这些线程当然是完全没问题的,这些线程只会触发less量的时间(比如一次一帧)。如果你的目标是PC,你应该记住核心数量(或者更好的HW – 线程)不断增长,所以你会想find一个可扩展的解决scheme,它利用了额外的CPU功耗。 所以,在这个领域,你应该尽量devise你的代码尽可能的基于任务。

线程应用程序的一般经验法则:每个CPU核心有1个线程。 在一个四核心的PC上,意味着4.如上所述,XBox 360然而有3个核心,但每个硬件线程2,所以在这种情况下,6线程。 在像PS3这样的系统上……祝你好运:)人们仍然试图找出答案。

我build议devise每个系统作为一个自包含的模块,你可以线程,如果你想。 这通常意味着在模块和引擎的其余部分之间具有非常明确的通信path。 我特别喜欢像渲染和audio这样的只读过程,还有'我们还有'的过程,比如读取玩家input的东西,以便进行处理。 要触及AttackingHobo给出的答案,当你渲染30-60fps时,如果你的数据是过去1/30秒1/60秒,这实际上并不会降低游戏的响应感。 永远记住,应用软件和video游戏的主要区别在于每秒处理30-60次。 然而,在同样的说明中,input可能是你想要保留在主线程中的东西之一,所以剩下的只要出现就会对它做出反应:)

如果你devise的引擎系统足够好,那么他们中的任何一个都可以从一个线程移动到另一个线程,以便在每个游戏的基础上更适当地平衡你的引擎。 从理论上讲,如果需要在完全分离的计算机系统运行每个组件的地方,也可以在分布式系统中使用引擎。

我为每个逻辑核心创build了一个线程(减去一个线程,负责主线程,顺便负责渲染,但除此之外,它也充当工作线程)。

我在整个帧中实时收集input设备事件,但是直到帧结束才应用它们:它们将在下一帧中起作用。 我使用类似的逻辑进行渲染(旧状态)和更新(新状态)。

我使用primefaces事件推迟不安全的操作,直到后来在同一个框架,我使用多个事件队列(作业队列),以实现一个内存的障碍,提供了一个熨斗保证操作的顺序,没有locking或等待(按照作业优先级的顺序locking空闲的并发队列)。

值得注意的是,任何作业都可以向同一个优先级队列发送子作业(更好,接近primefaces性),或者更高的作业(稍后在该帧中作用)。

鉴于我有三个这样的队列,除了一个线程之外的所有线程每帧可能正好停顿三次(在等待其他线程完成当前优先级下发出的所有未完成工作时)。

这似乎是一个可接受的线程不活动的水平!

我通常使用一个主线程(显然),我会添加一个线程,每当我注意到性能下降大约10至20%。 为了放下这样的一滴,我使用了visual studio的性能工具。 常见的事件是(不)加载地图的一些地区或进行一些繁重的计算。