【本文翻译自GPUView的开发者Matt的blog. https://graphics.stanford.edu/~mdfisher/GPUView.html 】
【 GPUview可以在 https://docs.microsoft.com/en-us/windows-hardware/get-started/adk-install 这里下载到】
GPUView是Matt在微软实习的时候和Steve Pronvost一起开发的。它的目标是是分析GPU的硬件、driver、app、CPU cores性能方面的交互。它和IceCap、Vtune、PIX等等标准的profile不同。此工具可以仔细查看CPU和GPU的交互,确定app是被CPU还是GPU限制住了,以及我们需要重新对哪一部分进行优化以提高资源的利用率。GPUView可以解决以下问题:
- 为什么我们错过了VSync的interval?
- 新的surface是否过度使用了GPU,导致了卡顿的现象?
- 优化CPU部分的code是否能提高性能?还是需要减少GPU部分的工作?
- 我们把task送给GPU的时间是否足够早?还是GPU一直在等CPU?
本文第一部分介绍GPUView的使用和理解,第二部分介绍一些使用GPUView对游戏场景进行性能相关的问题分析。
GPUView包含在windows performance toolkit里面,首先run log.cmd,然后结束后run log.cmd,会产生Merged.etl的文件,可以由GPUView进行识别。
.etl文件里面的内容包括
- CPU context切换
- kernel mode的入口和退出
- GPU event cmd buffer提交/resource创建/lock 等
- graphics driver report的事件 cmd buffer的起始和结束的时间
- 其他的影响性能的系统event page fault等
ETW log的overhead非常低,基本上开着log,最多影响1到2个fps
==============================
理解GPUView
==============================
安装GPUView的时候会有一个使用说明文件。不过,GPUView从一开始就是一个非常复杂而强大的工具。由于很多team需要更多新的GPUView的功能,GPUView越来越复杂了。如果只知道D3D API,却对系统是怎么batch这些API,以及最终GPU硬件是怎么响应不熟悉的话,使用这个tool确实需要费一些功夫。
==============================
Windows Vista Display Driver Model
==============================
在windows Vista之后,微软重新设计的显卡驱动,以适应多个显卡应用app同时运行的情况。理解这个模型对于理解显卡在windows上面的性能至关重要。首先,OS将每个进程中的D3D的deivce和自己的graphics context联系起来。每一个送到这个context的API call都和一系列的cmd绑定在一起。当有足够的cmd,或者API觉得可以flush当前的cmd buffer的时候,D3D API会将cmd buffer送到graphics kernel。这些cmd buffer不被立即处理,而是存在一个queue里面。显卡有一个queue存放正在处理的task。周期性的,当有queue中有空间的时候,graphics scheduler会被wake up然后将cmd buffer queue中的task放入到显卡work queue里面去。GPU scheduler试图和CPU的scheduler一样能够公平的处理各种task。GPUView能够让我们看到每个context的GPU queue和显卡的queue随着时间变化是怎么样的。显卡总是处理queue最前端的object,如果queue是空的,显卡处于idle状态。请注意,GPUView就是为了这样的driver model设计的,不支持windows XP。
==================
A CPU-only 应用
==================
我们先来讲一讲对于没有GPU的app,GPUView是什么样的。下面是在VS中编译一个工程的trace.
我们来解析一下。GPUView的横轴永远都是时间。最顶端一行是时间标尺。下面一行是GPU hardware queue,展现了现在的task在显卡上是怎么处理的,可以看到,我们这个trace上大部分的都是空的。在hw queue下面的绿色长方形是系统的每个进程。在每个进程中,CPU的graphics queue是第一个,然后是属于这进程的其他线程。因为系统中有许多线程,GPUView只显示主要的线程。第一个进程一般都是idel process,并由每个CPU core一个线程。每个core由不同的颜色标注出来,在每个时间间隔里面,每一个core是由一个线程使用,如果没有线程是work ready状态,则core为 idle thread占用。因为种种原因,处理器可以在不同的线程之间切换,比如线程执行时间片过期,线程想要sleep,或者被某个event给block了,或者更高优先级的线程处于work ready状态。
{现在来看上面这个CPU only的case,对于devenv.exe这个进程来说,它的devenec.exe!0x000000001e80d这个线程一开始是占用的白色的core0,然后是红色的core2,后来又是白色的core0,所以,core0对应的idle线程里面在这两段时间里面是没有显示的,即被其他线程占用,不是idle}
=======================
一个简单的GPU应用
=======================
以下是魔兽世界(wow.exe)的trace
首先注意这些蓝色竖线,代表VSync的interrupt。
GPUView支持导入symbol来提供有意义的stack trace。像我们现在看到的wow.exe里面有两个线程,一个是wow的主线程,另一个是Direct3D创建的用于管理cmd buffer submit的线程,入股哦我们有WoW的pdb的话,就可以看到每一个线程的入口函数
从上面展现的时间段来看,wow的主线程一直不停的向wow的cpu queue上提交work,然后操作系统将cpu的queue中的task提交到GPU的queue中,为了更好的看一下这些queue的行为,让我们看下面3个VSync段
queue的高度表示的是queue中packet的数量,最先的packet在最下面,新提交的在上面。一个packet通常是包含在一个cmd stream里面的很多API call的集合,准备发给显卡硬件去执行的。在GPU Hardware Queue底部的是现在被硬件执行的task。如果是空的话,说明GPU此时为空闲。直到hw把task完成之后,才会从CPU queue中消失。
有一些packet使用不同的颜色去标注的,以显示他的重要性。最重要的packet类型是由交叉线标记出来的。如果app希望达到60fps,需要在每一个VSync的时间内GPU硬件至少处理一个packet。
注意一下这里的dwm.exe进程,这个是desktop windows manager,这个是一直在跑的,除非你是处于全屏状态。检查dwm的行为很重要,因为如果你的应用影响了dwm的显示,你的app的改动就不会对用户可见了,不过因为windows给dwm.exe一个更高的gpu优先级,这种情况一般不会发生。
======================
单个queue packet
======================
GPUView对每一object存了很多信息。例如,你可以点击每一个queue packet来看看它的生命周期,以及一些其他的信息
这让我们可以清楚的看到从这个packet第一次进入到最后完成花了多少时间,这个在debug latency的问题的时候是很有用的。这个packet的起始时间是指API决定将cmd buffer发给queeu的时间。在d3d中有一些函数会force显卡api flush现在的cmd buffer,一些以前的有心,如warcraf 3,也会用一些lock frame buffer的技术来force显卡API去flush并处理cmd stream。
==============================
多个GPU应用
==============================
这展示了当多个GPU的应用同时在跑的时候,GPUView是怎么样的。每一个CPU的contex Queue都会有自己的颜色,所以很容易看到现在graphics硬件正在处理哪个task。GPUView中可以很清楚的看清这些事件。
=========================
问题诊断
=========================
现在我们来看看3个与游戏中低性能有关的问题,并研究下是什么原因引起的。
这是跑在Ultra setting下的SC2,mothership is cloaking 很多的小单元。这个cloaking effect很容易被注意到,在外接的taxing显卡上。从上面的trace来看,每一个rendering大约花了4个VSync的间隔,所以游戏的fps非常低,只有15。很明显,问题是由于render的时间过长导致的,单个queue packet花了2个VSync的间隔才完成。GPU的downtime非常短,大约只有5ms,可以通过更快的submite GPU cmd来cover。除此之外,CPU的优化都是无用的,唯一的提高性能的方式就是submit更少的工作量给显卡。
CPU延迟过长的案例
这个是魔兽世界的例子。CPU和GPU都是瓶颈,对这二者任何一个的优化都可以提高性能。最大的问题是在目前的buffer提交给GPU时有很大的延迟,前一个buffer提交之后,后一个几乎用了半个VSync间隔才提交,基本导致WoW不能达到60fps。为了解决这个问题,CPU的工作需要被推迟,这样一些graphic rendering就可以更早的submit,或者可以在多个线程之间去完成,这样减少CPU的延迟。这些方法都可以提高GPU的利用率,提高帧率。
GPUView周期性的会log stack trace,也允许app自己去提交自己的event到log stream,这样便于决定究竟是哪一个CPU的工作在某一时间段进行。
一个在PC上优化应用的比较讨厌的地方是每一个用户有自己不同的硬件配置,性能也不相同。下面是和上面那个case一样的场景,不过是在一个更慢的CPU和更低的显卡设置上。场景是类似的,CPU执行时间更加影响到了fps,减少送给显卡的工作几乎不影响帧率。
尽管只有一个魔兽世界的线程,WoW实际上创建了很多线程,在这些interval中也有在执行其他的代码。然而,GPUView自动隐藏了,因为他们几乎没有什么工作,而上面展现的线程几乎占了WoW的98%的执行时间。关于其他的线程,你可以看下面这张图
Excessive sleep - Ironforge
魔兽世界在Iroforge上跑在一个非常空的状态。尽管VSync的间隔为60fps,WoW跑在30fps。从上面这个trace,我们可以发现,尽管app没有被CPU或者GPU所限制,WoW的render线程在整个VSync间隔中都是asleep状态。比较有可能是sleep在一些graphics API的event上,但是由于通信出现了问题,导致WoW sleep了比预期更长的时间。看看再这个interval中的其他系统线程,可以发现还有很多core可以跑WoW线程。用GPUView导入更多的log可以看到是什么event导致线程被停止执行。
Event Lists
GPUView记录了每秒发生的很多的event,有一些可以直观的看到,比如context转换,queue packet submit,不过还有一些是不能直观看到的。用GPUView可以直接搜索每个stream,decode了一些最重要的event。例如,有些程序员担心allocation会引起frame卡住,所以需要高亮所有的allocation event,他们可以立刻放大到allocation比较集中的地方。
用户可以看关于每个allocation event的信息,像分配的大小,在哪个segement(system memory/video memory)。
GPUView还支持一些简单的dll model,app可以将被观察的stream和已知的code联系起来,并能解码对应的参数。