我目前正在开发游戏。我有一个渲染线程,主线程和一堆其他线程,这些线程在侧面进行计算。
典型框架设置如下:
在整个框架中,对工作线程进行了各种计算。
在运行游戏时,有些时候我无法获得理想的60 FPS。通过一些调试和分析,我得出的结论是,主线程和渲染线程的组合仅贡献了大约8毫秒的时间,这应该给我大约120 FPS。
我还注意到,当工作线程进行更多工作时,我倾向于看到FPS下降。
注意:FPS是根据主线程上每个帧之间的时间差计算的。
我会认为FPS应该仅取决于主线程和渲染线程的性能。
还要注意,主线程和渲染线程花费在等待工作线程上的时间可以忽略不计。
那我想念什么?尽管FPS不依赖那些线程,为什么当工作线程执行更多工作时FPS会下降?
EDIT :主线程以及Websocket线程提交要在工作线程上完成的工作。
经过更多分析后,似乎CPU在渲染线程完成其工作之后只是等待了一段时间(不确定原因),直到再次从主线程开始。请注意,我正在使用Unity3D,但是在C ++插件中做了很多自定义操作。
答案 0 :(得分:5)
在典型的现代计算机系统中,有很多共享资源,这些资源使内核不独立。
首先,CPU是具有温度的单个物理对象。如果CPU过热或消耗过多功率,则各种技术将以性能为代价将功耗降至最低。时钟速度提升技术将这些因素考虑在内。有些直接根据活动内核的数量进行限制。
CPU还具有共享资源,例如L2和L3高速缓存以及内存带宽。活动核心可以消耗这些资源,而其他核心则更少。
最后,糟糕的软件设计会在不应该存在的地方创建依赖关系。例如,虚假共享可能导致一个内核上的软件使另一内核上的软件运行效率降低。设计不良的同步原语(例如幼稚的自制自旋锁)会使内核间总线饱和,从而导致性能下降。
答案 1 :(得分:0)
几件事:
每帧专注于秒(实际上是毫秒),而不是每秒。根据您的驱动程序,渲染API,框架(如果有)等,您的游戏可能会被调回到60FPS,30FPS等,以使您偶尔或几乎没有错过目标时保持呈现流畅。例如:如果您未在11ms内持续渲染,则Oculus运行时将从90Hz降低到45Hz。
- 等待渲染线程结束。
不要让主线程(通常称为“游戏线程”)等待渲染线程完成。这使使用单独线程的许多目的无法实现。 Unity和Unreal都使用单独的游戏线程和渲染线程。在Unity中,游戏线程将渲染命令(或作业)推入队列,然后渲染线程从该队列中拉出。可以在队列中插入时间戳(或其他任何时间戳),这样,如果渲染线程落后太远,它就可以快进至最新帧,而不渲染旧数据。
同样,渲染线程不应等待其他线程。如果游戏停止更新但不冻结,则比其他方法更好。
根据您对系统的描述,不清楚您的“工作线程”在做什么。您说这个问题是断断续续的。它与当时的工作量有关吗,还是看起来完全是随机的?
您的分析是否表明问题出在CPU(主线程和工作线程)或GPU上?
一些要看的东西:
内存管理。堆颠簸是性能的敌人。如果您经常在紧密的循环中分配/取消分配非基元,则这可能会成为一个严重的问题。 (另外,查找内存泄漏。)
上下文滚动。由于您的主线程正在等待渲染线程,因此由上下文滚动引起的延迟将在系统的其余部分传播。
内容。您正在渲染什么,如何渲染?我曾在一个项目中工作过,有人试图将整个非常高多边形的宫殿作为一个整体模型进行渲染,而玩家则在所述宫殿内走来走去。这使得平截头体淘汰毫无用处。性能,我们可以说是次优的。
内容,第2部分:每帧要向GPU发送多少数据? CPU到GPU的带宽是有限的。如果每帧刷新较大的顶点缓冲区或纹理,性能将受到极大影响。
使用GPUView之类的工具并记录一些日志。 GPUView不是我最喜欢的工具,但是它可以让您了解这是CPU还是GPU问题。在带有DX12或Vulkan的AMD上,RGP(Radeon Graphics Profiler)很棒。 Nvidia也为此提供了一些非常出色的工具。
简而言之:
找出导致问题的线程以及原因。这将为您指明正确的方向。
答案 2 :(得分:-1)
-任何复杂的可运行3D图形引擎以及基于该引擎构建的随附游戏应用程序都有许多主要组件-
以下是要考虑的事项:
1 st -
Game Source Code
是否紧紧扎在游戏引擎中?
- 意思是,如果要剥离
Game Logic
,Game Properties
,Game Settings
和Loaded Game Assets
;Game Engine Break
会吗?- 或者
ALL
中的Game Engine
建立在Framework
与所有Abstracted
相距Game Contents
的{{1}}的基础上,成为完整的{{1} }和Generic
Reusable
?2 nd -
Library
- 计算机所基于的
System's Current Hardware And Configurations
。OS
和Installed
Running
和Appications
。- 最近的
APIs
,Installed
和Updated
Supported
。3 rd -您要为
Drivers
使用哪种Framework
?
Memory Module
,BSP
,OCT TREES
等4 th -正在加载哪种
Marching Cubes
?
- 它们是什么类型的内容?
- 正在加载多少内存?
- 它们的文件大小是多少?
- 它们如何存储在内存中。
- 正在使用哪种容器:
Content
,vector
,map
,list
等},- 您
array
,opening
和reading
是否在同一文件中多次?- 如果您创建了一个特定的资源并且在游戏中被多次使用,您是否会将其作为多个实例加载到内存中?
- 或者您只是创建一个实例,并在共享该实例时将其用作参考,就好像它是多个对象一样?
- 示例:您的内存中有一个加载的汽车网格,您的场景中有3种类型相同的汽车,但是它们的颜色不同:一种是蓝色,一种是红色,另一种是黑色;您是要为每个动态网格存储3个单独的网格,还是要在内存中存储一个实例并在发送要渲染的图像之前引用该内存以应用某种颜色转换?
- 正在读取的文件:
- 它们是
loading
Compressed
,Raw Data
还是Text
吗?- 他们有某种形式的
Binary
吗?- 您是否将
Encryption
与现代APIs
一起使用,如果使用Shaders
是哪种类型?5 th -现在我们提到了
Shaders
,这会导致绘制调用或渲染帧:
- 您的渲染系统具有哪种类型的结构?
- 如果您在图形渲染引擎中集成了动画,物理和/或粒子引擎,是否要将
Shaders
的回调速率与FPS
同步?6 th -整个场景:
- 您的
Physics Time Step Refresh Rate
的结构如何?- 您如何处理照明?
- 您如何处理阴影?
- 顶点电话抽奖:
- 使用索引顶点还是流式顶点绘制所有顶点?
- 您的
Scene Graph
,Vertex
,Fragment or Pixel
或Geometry
Compute
之间是否存在瓶颈?- 您使用的是
Shader(s)
还是Stencils
?- 您如何处理
Scissors
,Z-Depth
,Transparency
和Backface
Frustum
?7 th -编程技巧:
- 您仅使用
Culling
进行编程还是使用Multi Threading
?- 您是否错过了
Parallel Programming
,还是Cache
是您的Trashing
?- 您的共同结构是
Cache
WORD
吗?最常见的4字节对齐方式。- 您是否正确处理
Aligned
?- 您
Little - Big Endian
多久从一种类型转到另一种类型?- 您是否在
Casting
的高优先级critical sections
中找到code
并利用了loops
块?asm
上的短期对象与几乎总是stack
内存中的游戏长度对象之间的权衡是什么?- 重新回到您使用的是哪种容器,以及使用哪种算法来搜索,排序,查找,添加和删除元素?
8 th -关于系统本身:
- 正在运行哪种
dynamic
?- 您的
Background Applications
Engines
Main
有什么优先级?- 您的
Executable-Thread
正在运行扫描吗?- 您的
Antivirus
OS
是Updating
还是Application
?- 在您的
Driver
上有Traffic
个沉重的东西吗?第9 th -呈现类型:
- 您是在
Network
还是Software
中绘制边框?
- 如果在
Hardware
中是通过Hardware
进行绘图调用(例如在CPU
中),还是在使用现代的Legacy OpenGL 1.0
并将绘图调用发送到专用图形卡(APIs
)?- 该图形卡是
GPU
之类的真实卡还是PCI-Express
?10 th -CPU和批处理渲染上的GPU:
- 如果您使用的是
Integrated Graphics Chip
;被忽略的最重要的事情之一是如何将GPU
,vertex buffer
等发送到图形卡?- 您是否要发送一个要在每个帧中绘制一个模型的模型?
- 还是要将它们分配给优先级队列,以便一次通过发送多个相似类型的对象?
- 例如:您有一个帧函数,每秒被调用约60次,在此函数中,您有一个for循环,该循环正在遍历一堆3D模型及其所有顶点数据,颜色纹理数据,正常数据等...
- 是否在每个帧传递中调用自己的渲染或绘图调用函数?
- 或者您将相似的原始类型组合到一个大存储桶中,或者在优先级最高,已填充或所有其他存储桶都已填充且已满时发送该存储桶?
- 此过程称为
color data
。由于Batch Rendering
比GPU
快得多,因此使事情变得更加高效。它比CPU
可以完成更多的工作,但是通过CPU
发送信息被认为是一个相当缓慢的过程...- 因此,如果您在框架中,并且在“卡车”网格上调用渲染,然后在“树”网格上调用render,然后在“房屋”网格上调用render,等等。假设您有大约3,000个这些渲染调用在这个单一框架中;所有这些都必须在World-View-Projection矩阵中进行分类和说明,以便可以根据Z深度和透明度按正确的顺序将这些对象正确地渲染到屏幕上的正确位置,方向和比例的屏幕上。信息。
- 如您所见,这很快就会变得非常麻烦。因此,这里有很多工作要做。
- 了解这一点:如果在将数据发送到帧缓冲区之前对数据进行了预排序,并且正在使用批处理渲染器(而不是对每个对象进行3000次渲染调用);所有相似的对象都会被分组到一个存储桶中,在该存储桶中,优先级队列中有几组存储桶正在等待呈现或发送到
BUS
。如果时间合适,则会将适当的存储桶分配到GPU
进行对象渲染。除了我所提到的以外,还有很多,因为这只是
GPU
中的Tip
...我在Iceberg
上简短地提到过,{{ 1}},Physics
之类的东西,但我从未提及Animations
,Particle Systems
,Terrains
,Skyboxes-Domes
,Dynamic Weathering
和{ {1}},Ecological Systems
等...这仅在事物的Sound
方面;这些Music
,Networking
,Engine
,Game Loading
,Game Logic
,Game Level
等都没有。 >
-提示-
直接与线程和每秒的帧建立直接关系并不是那么简单,因为其中涉及许多组件,变量和因素。您的框架和渲染调用是否在线程内?还是您的框架函数中有很多线程?如果您似乎在应用程序中遇到瓶颈;逐步调试程序,并尝试将其范围缩小到使您变慢的关键密集循环。围绕不同的循环进行一些性能分析和基准测试;尝试更改循环中的数据集或数据量,以查看是否有效果。确保您没有运行任何无关的后台应用程序,以使基准测试无偏倚。在各种机器和平台上尝试代码...看看是否产生相同的结果。
最后一个问题或担忧是:您有运行引擎可执行文件的主线程,它产生了其他线程;一个用于渲染,一个用于处理音频,一个用于加载到对象(网格模型)文件中,等等。这些线程如何同步;你有比赛条件吗?您是否正在使用互斥锁,信号灯或栅栏等?这些因素对您的FPS的影响极小,甚至是戏剧性的,这还不包括我提到的所有上述因素!
-伪代码示例:-
编写您的Game Engine的目的是在单核上的单线程中运行,或者设计其目的是在多个线程和/或核上甚至并行运行。
最重要的是:您的游戏引擎-您主体中的游戏代码通常如下所示:
Game Properties
Game Settings
将调用Game Rules
#include "Game.h" // Game.h includes Engine.h as it inherits from Engine.
int main () {
try {
Game game;
game.run();
} catch(...) {
// Display Exception: Error MSGs -Exit Error
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
,即操作系统的Game.run()
-系统(I / O),并且将持续进行直到用户决定退出。
Engine's
-结论-
如果引擎内没有多线程-游戏本身;那么它不是引擎的集成部分,而是从引擎中抽象出来的,整个设计将是一个单线程应用程序。尽管您可以在main.cpp生成其他独立线程的同时在其自己的线程中运行游戏引擎,但仍将其视为单线程组件。
或者您可以在整个游戏引擎中使用多线程,然后对其进行裁剪,以在多个内核线程上运行……在这种情况下,它将成为引擎以及该引擎所有组件的集成部分。引擎会影响您每秒的整体帧数!
不仅在理论上,而且在实践中,如果您在应用程序框架中使用多线程,那么与FPS的关系实际上是至高无上的,因为它是整个游戏引擎结构与设计的有机组成部分。
现在与您的总体评估有关:它可能根本与您的线程无关;它可能与给定时刻在场景中渲染多少个对象有关,如果您启用或禁用了背面剔除,以及启用和禁用了视锥体,则还取决于视锥体z深度和视角。这也取决于网格纹理中的细节量。您如何计算顶点及其法线,这取决于您拥有多少个光源;您正在使用哪种类型的照明。它甚至可能不在您的frame()
身边。这可能是您的着色器中的瓶颈:请记住,片段着色器的调用要比可编程着色器管道中的顶点着色器多得多。
一个提示或建议是设置将当前FPS报告给观察点的变量,并在低于给定阈值的时间超过给定时间的情况下中断该条件。您的休息条件可能看起来像这样:
messageHandler()
从这里开始,您应该能够进入或退出应用程序以缩小瓶颈。
起初我以为这可能是种族状况。但是看来您已经排除了...