【发布时间】:2019-12-23 19:08:23
【问题描述】:
在渲染帧时,我的 C# 应用程序出现意外峰值。我一直在分析器中查看它,并注意到以下内容:
- 当 vsync 开启时,程序似乎在交换缓冲区时放弃了对操作系统的控制(或做某事)...并且在呈现时以某种方式保持非常一致。这很好,因为它可以平滑地渲染帧并且没有这种奇怪的微卡顿。下图显示了被认为是完美流动的东西。这就是我们的案例“它通过平滑渲染而完美地工作,没有任何卡顿”。
- 当 vsync 关闭并且程序什么都不做,只是尽可能快地写入而没有任何中断,即使每秒帧数更高,似乎也存在这些大的间隙,什么都不做。我很确定这是我在游戏中注意到的微卡顿,因为它会导致 FPS 急剧下降到 60fps 以下。下图显示出了问题:
在上面,增量时间为 21 毫秒(超过 16.6 毫秒,每秒 60 帧),这会导致游戏出现明显的卡顿。它甚至比这更糟糕,因为它只是在它之后才开始渲染(正如您在之后的实心矩形中看到的那样),所以这 21 毫秒是一个谎言,根据图表,它更像是 40 毫秒,这太可怕了。
由于我不明白的原因,当 vsync 处于打开状态时,从不会发生这种巨大的差距。对于那些不熟悉 vsync 和游戏的人,你不能在第一人称射击游戏中使用 vsync,因为它的工作原理会削弱输入处理,所以我不能使用 vsync,我必须调查非 vsync 版本。我需要找出为什么非 vsync 版本有这些主要的停顿。
我的问题是,我怎么知道是什么导致了您在上面看到的图像的这种延长延迟?
性能分析器说这里有大量的等待时间,超过 80% 的等待时间和 20% 的 CPU 使用率(与准备渲染数据时的 100% 的 CPU 使用率相比)。
分析器还显示,在该循环中没有运行任何渲染代码......这很奇怪,因为渲染器几乎完全控制了性能,所以如果没有每秒帧数的任何上限,它应该会淹没整个图形一个纯蓝色矩形。
问题是分析器显示的代码只是调用了我上面选择的区域中的轮询输入和 dll:
请注意,DispatchRenderFrame 的另一个调用是在进行 OpenGL 调用,当我完全删除它时对程序没有影响,因此您可以忽略它。这可能意味着下面看到的用户输入对微口吃问题也没有影响......但我无法删除它,因为它是我用于窗口管理 (OpenTK) 的库的一部分。
我不确定CLR Worker 线程是什么或它在做什么。它也发生在 vsync 之一(所需的平滑分析)中,所以即使我不知道它是否是这里的罪魁祸首,我的猜测是它可能不是但我不确定,因为它出现在 '所需的 vsync 示例也位于相同的一般位置。
是否发生了一些中断并且操作系统正在接管但没有恢复我的线程,因为它被归类为 CPU hog?只是一个想法...但又在示例中显示蓝色条,所以我假设 Main 线程实际上并没有在我突出显示的内容中处于睡眠状态并且实际上正在运行?
如您所见,我不确定我突出显示的时间片告诉我什么,我需要帮助。我不知道为什么此时代码中的等待百分比如此之高,也不知道从哪里进一步诊断问题。
编辑:我对@987654331@ 类进行了一些粗略的分析,以查看哪些位置导致了尖峰,并且尖峰仅来自计算部分。 OpenGL 调用都不会导致任何延迟。它纯粹发生在执行数学运算和访问数据或写入预先分配的结构数组的函数内部。
补充说明:
这也让我很好奇,是否有一种方法可以强制 C# 虚拟机获得尽可能多的 CPU 使用率?
没有产生垃圾。这不是一个 GC 问题。我不能在应用程序期间运行 GC,所以我不会产生垃圾。渲染函数中的所有内容都是堆栈上的结构,唯一一次它进入任何堆管理对象是一个池化数组,该数组的大小对于所有渲染数据来说都足够大。它在分析器中显示的唯一时间是在开始和结束时,但它不会在渲染阶段运行。
【问题讨论】:
-
好吧,您已经关注并研究了代码,但是您是否研究过正在运行的硬件?再次重新测试,检查 CPU、内存和 GPU 使用情况,看看它们是否有任何尖峰。然后,从 Nvidia 设置中,确保电源选项设置为最高性能,然后再次重新检查。 (暂时不要更改代码中的任何内容)。
-
@iSR5 电源选项设置为最大性能前后都按照你说的做了,前后都一样。
-
有几点值得注意:A) 不保证结构体在堆栈上......他们赞成这样做,但运行时可能会将它们移动到其他地方。 B) 如果你没有通过
ref传递它们,它们肯定会被移动到堆中。 -
@Mgetz,这是不正确的。
structs 在按值传递时不会移动到堆中,没有ref/in。它们是使用线程的堆栈复制的。 -
@dymanoid
in the Microsoft implementation of C# on the desktop CLR, value types are stored on the stack when the value is a local variable or temporary that is not a closed-over local variable of a lambda or anonymous method, and the method body is not an iterator block, and the jitter chooses to not enregister the value.blogs.msdn.microsoft.com/ericlippert/2010/09/30/…
标签: c# performance opentk