2

作为优化我的 3D 游戏/模拟引擎的一部分,我正在尝试使引擎进行自我优化。

基本上,我的计划是这样的。首先,让引擎测量每帧的 CPU 周期数。然后测量各种子系统消耗的 CPU 周期数(最小值、平均值、最大值)。

鉴于此信息,仅在帧循环中的几个特定点,引擎可以估计有多少“额外 CPU 周期”可用于执行现在可以有效执行的“可选处理”(相关数据现在在缓存中),但如果当前帧有运行不足 CPU 周期的危险,则可能会延迟到某个后续帧。

我们的想法是在繁重的工作上尽可能领先于游戏,因此每个可能的 CPU 周期都可用于处理“要求苛刻的帧”(如“单帧期间的许多碰撞”),而不会调用 glXSwapBuffers( ) 及时在 vsync 的最新可能时刻之前交换后/前缓冲区)。


上述分析假定交换后/前缓冲区是确保恒定帧速率的基本要求。我见过声称这不是唯一的方法,但我不明白其中的逻辑。

我在 glXSwapBuffers() 之前和之后捕获了 64 位 CPU 时钟周期时间,发现帧相差大约 2,000,000 个时钟周期!这似乎是由于 glXSwapBuffers()直到 vsync(当它可以交换缓冲区时)阻塞,而是立即返回。

然后我在 glXSwapBuffers() 之前添加了 glFinish(),这将变化减少到大约 100,000 个 CPU 时钟周期......但随后 glFinish() 被阻塞了 100,000 到 900,000 个 CPU 时钟周期(大概取决于多少工作 nvidia 驱动程序必须在交换缓冲区之前完成)。由于 glXSwapBuffers() 完成处理和交换缓冲区可能需要多长时间的这种变化,我想知道是否有任何“智能方法”有希望。


最重要的是,我不确定如何实现我的目标,这似乎相当简单,并且似乎并没有对底层子系统(例如 OpenGL 驱动程序)提出太多要求。但是,即使在 glXSwapBuffers() 之前使用 glFinish(),我仍然看到“帧时间”有大约 1,600,000 个周期变化。我可以平均测量的“每帧 CPU 时钟周期”速率,并假设平均值产生实际帧速率,但是由于变化如此之大,我的计算实际上可能会导致我的引擎通过错误地假设它可能取决于这些值而跳过帧。

对于所涉及的各种 GLX/OpenGL 函数的细节,或者在实践中可能比我尝试的更好的一般方法,我将不胜感激。

PS:当核心减速或加速时,我的 CPU 的 CPU 时钟频率不会改变。因此,这不是我问题的根源。

4

2 回答 2

1

这是我的建议:在渲染结束时只需调用交换缓冲区函数并在需要时让它阻塞。实际上,您应该有一个线程来执行所有的 OpenGL API 调用,并且仅此而已。如果还有其他计算要执行(例如物理、游戏逻辑),请使用其他线程,操作系统将让这些线程运行,而渲染线程正在等待 vsync。

此外,如果有些人禁用了垂直同步,他们想看看他们可以达到每秒多少帧。但是使用您的方法,似乎禁用 vsync 只会让 fps 大约 60 左右。

于 2015-01-27T04:38:36.220 回答
1

我会尝试重新解释你的问题(这样如果我错过了什么你可以告诉我,我可以更新答案):

鉴于T是在 Vsync 事件发生之前您可以使用的时间,您希望使用 1xT秒(或接近 1 的时间)来制作帧。

但是,即使您能够编写任务代码,以便它们可以利用缓存局部性来实现完全确定性的时间行为(您提前知道每个任务需要多少时间以及您有多少时间可供使用),所以理论上您可以达到以下时间:

0.96xT

0.84xT

0.99xT


你必须处理一些事实:

  1. 你不知道 T(你试图测量它,它似乎打嗝:那些是依赖于驱动程序的!)
  2. 计时有错误
  3. 不同的 CPU 架构:您测量一个函数的 CPU 周期,但在另一个 CPU 上,由于更好/更差的预置技术或流水线,该函数需要更少或更多的周期。
  4. 即使在同一个 CPU 上运行,另一个任务也可能会污染 prefeteching 算法,因此相同的函数不一定会导致相同的 CPU 周期(取决于之前调用的函数和 prefetech 算法!)
  5. 操作系统可能会在任何时候通过暂停您的应用程序以运行一些后台进程来进行干扰,这会增加您“填充”任务的时间,从而有效地使您错过 Vsync 事件(即使您的“预测”时间是合理的,例如0.85xT

在某些时候你仍然可以得到一个时间

1.3xT

同时您没有使用所有可能的 CPU 功率(当您错过 Vsync 事件时,您基本上浪费了帧时间,因此浪费了 CPU 功率)


您仍然可以解决方法;)

缓冲帧:您最多可以存储 2/3 帧的渲染调用(不再有!您已经添加了一些延迟,并且某些 GPU 驱动程序会做类似的事情来提高并行度并降低功耗!),之后您使用游戏循环来闲置或做迟到的工作。

使用这种方法,超过1xT是合理的。因为你有一些“缓冲帧”。

让我们看一个简单的例子

  • 您为0.95xT安排了任务,但由于程序运行在与您用于开发程序的 CPU 不同的机器上,因为架构不同,您的框架需要1.3xT
  • 没问题,你知道后面有一些帧,所以你仍然可以开心,但现在你必须启动1xT - 0.3xT任务,更好地使用一些安全边际,所以你启动0.6xT而不是0.7xT的任务。
  • 操作确实出了问题,帧再次占用了1.3xT,现在你用尽了你的帧储备,你只需做一个简单的更新并提交 GL 调用,你的程序预测0.4xT
  • 令人惊讶的是,即使您安排了超过2xT的工作,您的程序也为以下帧花费了0.3xT,您再次在渲染线程中排队了 3 帧。
  • 由于您有一些框架并且还有后期作品,因此您安排了1,5xT的更新

通过引入一点延迟,您可以充分利用 CPU 能力,当然,如果您测量大多数情况下您的队列缓冲的帧超过 2 个,您可以将池减少到 2 个而不是 3 个,这样可以节省一些延迟。


当然,这假设您以同步方式完成所有工作(除了延迟 GL 校准)。您仍然可以在必要时使用一些额外的线程(文件加载或其他繁重的任务)来提高性能(如果需要)。

于 2015-01-27T17:42:23.560 回答