UE高级性能剖析技术(1)-- RHI线程(渲染提交)
在最前面
基于UE的手游客户端的性能主要由这七大部分构成:CPU逻辑,CPU渲染,图形API(提交),GPU渲染,内存,带宽,加载时间。这几个基本元素又会合力衍生出一些新的性能指标,例如功耗(往往同gpu负载和带宽紧密相关)。同时这七部分又构成一个闭合的木桶,最长的一块是主要瓶颈,并且瓶颈可以在这几块转移流动。作为开发者我们解决性能问题的步骤一般都是按照做性能剖析,解读结果,定位问题,增加剖析代码,优化问题,重复剖析的迭代过程来执行。而高效准确详细的对性能进行剖析得到结果是第一步,在任何引擎上,只要我们能做到在任意时刻准确的获取想要的性能剖析结果,那么才会胸有成竹不会慌,该系列文章将归纳总结在ue下对每一性能指标的剖析方法,做深入分析,我们需要工具的帮助,也需要程序员理解引擎并知道如何去编写合适的剖析代码。
最近刚好做过一轮RHI线程的剖析,第一篇就从RHI开始,我会坚持把后面几篇写下去。
渲染API瓶颈
渲染API瓶颈是3D手游的常见瓶颈,我们常说的drawcall 过多了,卡渲染就是指的卡在这里,其实这个卡渲染卡的是cpu。为什么drawcall会卡,因为cpu需要通过对渲染api的调用来驱动gpu做事情,1个drawcall的背后是一堆渲染api的调用,下面是一个常见的drawcall过程,
可以看到为了一次绘制(1个drawcall),要设置shader,创建buffer等等,这些相比最后的draw那一步来说都是相对更费时的。
当测试反馈给我们卡drawcall的时候,作为程序我们需要一种手段来衡量出确切的当前做哪些drawcall,或者说绘制哪些东西更耗,最好是精确到耗在绘制哪个模型的哪个api调用上,我们才能真正的给美术予以优化指导。
UE中精确定位RHI瓶颈
在UE中,pc和android平台通常渲染api的调用会放在一个单独的线程,叫做RHI线程,这个线程专门负责渲染指令的提交,即调用显卡的API。我们分析渲染提交的卡顿就是要分析这个RHI线程。
多线程渲染工作模型
但是RHI线程不是单独存在的,它需要同game,render线程协作,rhi的卡顿可能不只是rhi的卡顿,首先需要清楚UE里面RHI线程和其他线程的工作模式:
这里面game render rhi gpu分别在4个并行的工作线上,有这样几个特点:
- game thread最多可以等渲染一帧,也就是说渲染如果第N帧的渲染在第N+1帧的game tick结束时还没有完成,那么渲染就会把game卡住,render 和rhi不会有帧延迟。
- game是render和rhi的源驱动者,game的卡顿可能会卡住渲染
- render 负责产生drawcall,rhi负责提交drawcall,因此render的卡顿也可能卡住rhi提交。
- 渲染的最后一步要swapbuffer,即等待gpu完成,所以gpu的卡顿也可能会卡住rhi。
- 除了gamethread本身,render rhi 和gpu的工作都是存在间隙的,即game逻辑喂给渲染任务的时机会影响渲染工作的密度,也会影响到渲染的时间,小量多次会浪费渲染效率。
UE中rhi的瓶颈的来源
现在我们知道rhi的卡顿可能来自于以下几种情况:
a RHI指令自身的卡顿,即通常所说的卡drawcall,过多的dc,过多的渲染状态切换,过多的渲染资源创建,等等;
b game或者render thread的卡顿
c gpu的卡顿。
对于情况b,我们可以通过UE的status 看当前的game和render的线程执行时间来容易的判断出来,来排除是rhi上出了问题。
对于情况c, UE的status中在rhi线程上会统计一个叫做swapbuffer的时间,如果这个时间过长,那么就是gpu瓶颈了。
真正比较麻烦的是定位情况a,即对于rhi指令本身的卡顿瓶颈。对于这种情况UE自带的stat工具通常不能给出比较有力的分析结果,自带的方法只能统计一帧在rhi上做几种给定操作的时间,但是在复杂的线程条件下,有时很难确定这些卡顿的幕后原因,有时rhi问题只是一个表象,为了得到rhi线程瓶颈的确切原因,我们至少要能够明确以下几个事情:
1 Rhi线程的执行是由一堆有序的rhi command组成的,我们要能捕捉到具体的那一个rhi command的执行时间比较长,比如是创建场景中哪个房子的vb?
2 是在render thread的哪一个步骤塞入的渲染数据导致了这个rhi command执行的时间比较长,是在渲染阴影的时候,还是渲染basepass,还是做遮挡剔除?
3 是在game thread的哪一个步骤塞入的渲染数据导致了这个rhi command执行的时间比较长?是在加载场景?还是在绘制UI的时候?
笔者在项目中遇到过一个问题,在一些低端机,rhi会有时突然卡顿几秒以上,看stat文件如下:
。
我只能看到在rhi 线程的thcikbegin阶段发生了巨大的卡顿,然后就没有细节了,不知道是具体哪个rhicommand,然后看gamethread在wait,也不知道是game thread的哪一步触发了这个rhi瓶颈。我们需要一些办法。
定位UE中rhi线程的瓶颈
我们需要分别将上面三种原因捕捉到,就能解开这个问题。
首先定义一个宏,只有我们需要捕捉这些详细的rhi瓶颈时开启,因为这些操作会存在较大的overhead。
定位具体rhicommand的时间
对于rhi command的具体执行时间,我在FRHICommand的最终执行阶段ExecuteAndDestruct中创建一个FScopeCycleCounter,counter的名字就直接rtti当前command的typename。
有时候我们需要更细节的知道这个command除了类型外的信息,例如如果这是一个createvb的command,那么vb的原始模型名字是什么,vb大小等,我在一些command处额外传了一些debug用的string,然后在这些command的执行前补上一个FScopeCycleCounter。这样我们就能拿到精确到具体rhicommand的提交耗时了。经过这个补充,我能拿到这样的rhi 线程执行时间统计:
这样谜底就清晰了很多,原来这时候存在大量的vb创建,数了一下,有几百个,在同一帧内几百个vb的创建,在低端android上会产生5秒钟的超级卡顿,那么问题来了,为何在这一帧会同时产生这么多的vb卡顿,是game或者render 上发生了什么事情,如果我们查看当前的game thread ,它显示的是wait,是不知道原因的,因为game ,render ,rhi是分开工作的,我们现在rhi处于瓶颈已经不是事故的”第一现场”了,我们需要进一步让你发生在第一现场。
定位在render的哪个阶段发生了rhi的瓶颈
UE的rhicommandlist自带了一个函数FRHICommandListImmediate::SetCurrentStat,可以用来让render给rhi加一个标记,这个标记就可以认为是render的某个阶段的名字,UE自带了在render 的很多阶段下了这个标记,我们还可以自己补充,这个函数的原理如下:
这个status本身也是以command的形式插入队列,所以每一条rhi执行的cmd会被统计到它之前最近的那个status tag下面,通过不断的细分插入这些tag,我们可以跟踪到rhi的cmd从是在render的哪个阶段被产生。需要注意的是这个tag只能在render thread里插入。我为render thread补充了一些细化的tag后,如前面的图,我发现这个大量的vb创建发生在渲染线程的一帧渲染结束到下一帧渲染开始之前,在这个阶段有game 逻辑往render 里面堆入了大量创建vb的指令,所以问题还要继续往game thread 上找”第一现场”。
定位在Game的哪个阶段产生RHI瓶颈
其实我们仍然可以模仿renderthread一样在game thread上给rhi的command list里面插入tag,但是有个问题,renderthread是一种相对简单的render command的队列的顺序执行, tag量有限相对容易操作,但是game thread里面逻辑极其复杂,我们希望可以复用game thread上面已经埋好的一些scope counter,不过game和rhi是两条并行的thread,需要在我们关心的scope处让二者能够强行同步住,才能容易的使用game 自己的scope counter抓住rhi的执行。我们这样去实现,假设下面是我们关心的一个game thread的区段,在前后加上代码如下
#if STAT_RHI_ADVANCED
FlushRenderingCommands(true)
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("XXX "), STAT_XXX, STATGROUP_RHI_GAME_SYNC);
#endif
//game 代码段
…
…
//
#if STAT_RHI_ ADVANCED
FlushRenderingCommands(true);
#endif
FlushRenderingCommands(true)的意思是在这个位置强制将所有当前的rhicommand执行完毕,阻塞住当前线程,所以上面这个代码段的原有的XXX统计的时间将包括这段时间内因为game thread上发生的渲染事件的渲染而花费的时间。
通过在game thread的主要逻辑处,插入这些同步rhi线程的代码,当rhi线程发生瓶颈的时候,我们只要查看当前gamethread在哪里停住(wait event),就可以判断是什么game 逻辑导致了rhi线程的瓶颈。有了这个机制,我们接着截stat文件,会看到当rhi处于巨大瓶颈时,game thread停在了这里:
凶手被抓住了,是一个资源正在被缓存池预加载!
这个奇怪的rhi上的卡顿的真正原因其实是game 线程上在加载一个模型资源!如果只依靠ue本来的stat分析,是无论如何都不可能猜到这个幕后的凶手的。
那么问题来了,一个资源的加载为何会导致海量的vb同时创建?通过进一步的分析代码,会发现因为这里用的是同步加载,而UE的同步加载的机制,是创建一个加载任务堆到同异步加载一样的加载队列里,因为不能保证依赖关系,所以要等待当前所有队列中的任务完成才能继续下去,也就是说当前的同步加载的时间绝不仅仅是加载完你要的这个模型而已,他需要将当前异步加载任务在队列中的所有资源加载完!而这个时候恰恰处于场景在level streaming的阶段,最后发现此事加载队列中的资源上百个,这个同步加载遇上level streaming的结果就是,在这一帧要完成上百个模型的创建,模型的postload会初始化rhi资源,导致一帧内大量vb的创建,卡死rhi,所以罪魁祸首是同步加载,同步加载将level streaming的过程也强行同步了,找到了问题,我们就可以通过相关的优化手段来排除这个瓶颈。
RHI上的问题可能往往不只是rhi上的问题那么简单,通过上面说的一些方法我们可以清楚的看到各种rhi上瓶颈的真正原因。
UE高级性能剖析技术(1)-- RHI线程(渲染提交)相关推荐
- 腾讯游戏学院专家:UE高级性能剖析技术之RHI
导语如何高效准确详细的对性能进行剖析?腾讯游戏学院专家Leonn将从RHI(渲染提交)开始,归纳总结在UE下对每一性能指标的剖析方法. 基于UE的手游客户端的性能主要由这七大部分构成:CPU逻辑.CP ...
- 《高性能MySQL》——服务器性能剖析(笔记)
文章目录 三.服务器性能剖析 3.1 性能优化简介 3.1.1 通过性能剖析进行优化 3.1.2 理解性能剖析 3.2 对应用程序进行性能剖析 3.3 剖析MySQL查询 3.3.1 剖析服务器负载 ...
- 《高性能MySQL》 第三章 服务器性能剖析 读书笔记
性能优化 基于响应时间 性能剖析 测量任务所花费的时间 对结果进行统计和排序,将重要的任务排到前面 时间分类 执行时间 等待时间 理解性能剖析 值得优化的查询 一些只占总响应时间比重很小的査询是不值得 ...
- 剖析Unreal Engine超真实人类的渲染技术Part 3 - 毛发渲染及其它
目录 四.毛发渲染 4.1 毛发的构造及渲染技术 4.1.1 毛发的构造 4.1.2 Marschner毛发渲染模型 4.1.3 毛发的间接光照 4.2 毛发的底层实现 4.3 毛发的材质解析 4.3 ...
- 高质量解读《高性能mysql》——第3章服务器性能剖析
前言: 高效读书,一张逻辑图带你读懂.读薄书中重点. 深入学习MySQL系列,解读的目的是为了把书读薄,抽出重点进行梳理.理解.运用.因大量文字很容易让人觉得枯燥无味,为此博主花费一定精力和时间整理输 ...
- 21 项优化 React App 性能的技术
原文: 21 Performance Optimization Techniques for React Apps 作者:Nishant 译者:博轩 介绍 在 React 内部,React 会使用几项 ...
- Unix网络编程(六)高级I/O技术之复用技术 select
转载:http://blog.csdn.net/michael_kong_nju/article/details/44887411 I/O复用技术 本文将讨论网络编程中的高级I/O复用技术,将从下面几 ...
- 测试服务器性能常用算法,服务器性能剖析(profiling)之——简介
性能剖析(profiling)是专注于测量服务器时间花费在哪里的一种技术,这里"性能即响应时间". 测量是一项很有挑战性的工作,并且分析结果也同样有挑战性,测出时间花在哪里,和知道 ...
- #周末课堂# 【Linux + JVM + Mysql高级性能优化班】(火热报名中~~~)
Linux + JVM + Mysql高级性能优化课程 课程名称: Linux + JVM + Mysql高级性能优化 QQ群: 243242580(小白.菜鸟勿进)跟Java.M ...
最新文章
- 重改mysql名字_如何快速重命名MySQL数据库(更改模式名称)...
- linux安装教程6.3,CentOS 6.3安装教程(详细图解)
- iOS Storyboard创建APP 的国际化操作
- 【开源推荐】进阶实战,从一款音乐播放器开始
- “AI+”农业向农民致敬-丰收节交易会:谋定工业反哺农业
- 隐藏帐户与隐藏权限的添加
- 持续集成部署Jenkins工作笔记0016---测试验证整个自动化持续集成流程
- activiti学习笔记---常见异常
- [Java] 蓝桥杯BASIC-13 基础练习 数列排序
- idea中mapper.xml的头文件配置
- [渝粤教育] 广东-国家-开放大学 21秋期末考试大学英语210262k2
- Java基础-运行原理及变量(01)
- 联想教育应用使用说明(7.6版本号)——第4章 网络控制工具的使用
- Typora下载连接及使用教学
- 关于NPOI在word表格中插入行的问题
- linux v4l2色调,V4L2下摄像头的详细参数调整
- 60.windbg----as、$u0(固定别名、自定义别名)
- 陈怡暖:FED决议措辞偏鹰,黄金先扬后抑险守千二关
- java版Spring Cloud+短视频带货 b2b2c多商户分布式微服务
- 52、尽量减少恶意软件的传播
热门文章
- 第三篇:如何精准表达,高效沟通
- CHINAPLAS线上展会启动!足不出户发现全球热点橡塑科技
- 带权二分图匹配(最小费用最大流) 8.2牛客暑期多校训练营五 E
- java开闭原则_什么是开闭原则?如何实现开闭原则
- w怎么接显示 树莓派zero_别出心裁——无显示器初次配置ssh连接树莓派zeroW的另类方法...
- 企业哪些地方需要计算机等级证,计算机二级证书的含金量高吗 有哪些地方用得到...
- 大数据讲课笔记3.3 Hadoop集群配置
- 处理文件时遇到:The process cannot access the file because it is being used by another process.
- 3D图形和网络编程的一次实践——昆特牌Online
- php微信退款回调通知,微信退款异步回调通知