GPUView的使用

本文介绍了GPUView,一款由Microsoft实习生开发的工具,用于分析GPU、驱动、应用和CPU性能交互。文章详细讲解了GPUView的使用、WindowsVistaDisplayDriverModel、不同应用场景(如CPU-only应用和多GPU应用)以及如何通过GPUView诊断和优化性能问题。
摘要由CSDN通过智能技术生成

本文翻译自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第1张

我们来解析一下。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

[译]GPUView第2张

首先注意这些蓝色竖线,代表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段

[译]GPUView第3张

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来看看它的生命周期,以及一些其他的信息

[译]GPUView第4张

这让我们可以清楚的看到从这个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应用

==============================

[译]GPUView第5张

这展示了当多个GPU的应用同时在跑的时候,GPUView是怎么样的。每一个CPU的contex Queue都会有自己的颜色,所以很容易看到现在graphics硬件正在处理哪个task。GPUView中可以很清楚的看清这些事件。

=========================

问题诊断

=========================

现在我们来看看3个与游戏中低性能有关的问题,并研究下是什么原因引起的。

[译]GPUView第6张

这是跑在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延迟过长的案例

[译]GPUView第7张

这个是魔兽世界的例子。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,减少送给显卡的工作几乎不影响帧率。

[译]GPUView第8张

尽管只有一个魔兽世界的线程,WoW实际上创建了很多线程,在这些interval中也有在执行其他的代码。然而,GPUView自动隐藏了,因为他们几乎没有什么工作,而上面展现的线程几乎占了WoW的98%的执行时间。关于其他的线程,你可以看下面这张图

[译]GPUView第9张

Excessive sleep - Ironforge

[译]GPUView第10张

魔兽世界在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比较集中的地方。

[译]GPUView第11张

用户可以看关于每个allocation event的信息,像分配的大小,在哪个segement(system memory/video memory)。

GPUView还支持一些简单的dll model,app可以将被观察的stream和已知的code联系起来,并能解码对应的参数。

以下是使用 PyTorch 在 GPU 上训练深度学习模型的示例代码: ```python import torch # 检查 GPU 是否可用 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") print(device) # 定义模型 class MyModel(torch.nn.Module): def __init__(self): super(MyModel, self).__init__() self.conv1 = torch.nn.Conv2d(3, 32, kernel_size=3, stride=1, padding=1) self.conv2 = torch.nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1) self.fc1 = torch.nn.Linear(64 * 16 * 16, 256) self.fc2 = torch.nn.Linear(256, 10) def forward(self, x): x = torch.nn.functional.relu(self.conv1(x)) x = torch.nn.functional.relu(self.conv2(x)) x = x.view(-1, 64 * 16 * 16) x = torch.nn.functional.relu(self.fc1(x)) x = self.fc2(x) return x # 加载数据 train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=32, shuffle=True) # 初始化模型和优化器 model = MyModel().to(device) optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # 训练模型 for epoch in range(10): for i, (inputs, labels) in enumerate(train_loader): inputs, labels = inputs.to(device), labels.to(device) optimizer.zero_grad() outputs = model(inputs) loss = torch.nn.functional.cross_entropy(outputs, labels) loss.backward() optimizer.step() if i % 100 == 0: print("Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}".format(epoch+1, 10, i+1, len(train_loader), loss.item())) # 保存模型 torch.save(model.state_dict(), "my_model.pt") ``` 其中,`device` 变量检查 GPU 是否可用,`model.to(device)` 将模型移动到 GPU 上,`inputs.to(device)` 和 `labels.to(device)` 将数据移动到 GPU 上,从而实现在 GPU 上训练模型。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值