从总体上来说,Windows10是一个好系统,虽然我们天天戏称它为“Bug10”,但不可否认的是,从立项以来,开发团队就一直在努力为它加入新的功能,其中有不少是相当实用的,比如说,他们在1709这个大版本中,为任务管理器加入了GPU性能监控单元,用户可以通过任务管理器直观地看到目前的GPU占用率,比以往要开GPU-Z等程序方便了不少。但很多用户在实际使用的时候也发现了,这个针对GPU的性能监控好像不太准,我显卡在全力计算的时候,任务管理器里面的GPU占用率怎么这么低?

比如我开个挖矿程序,显卡的占用其实是满的,但左边GPU窗格中显示的占用率只有3%

为了找出答案,我们找到了当时引入这项新功能时,开发者的讲解Blog,由于是与图形相关的内容,这篇Blog被归入DirectXDeveloperBlog中。

首先开发者给我们讲述了任务管理器是怎么得知GPU的占用情况的。在Windows10上面,GPU通过WindowsDisplayDriverModel(WDDM,Windows显示驱动模型)抽象,它的核心——图形内核——负责抽象、管理和在所有进程分配GPU资源。它含有一个GPU事务器(VidSch)和一个视频内存管理器(VidMem),前者负责将GPU的各种引擎分配给想要使用它们的进程,并对访问进行仲裁和优先级排序,后者则是负责管理GPU可调用的内存——包括专用的显存和共享的系统内存。

任务管理器就是通过VidSch和VidMem回报的数据来计算GPU的使用情况的,这样一来,不管程序使用了什么API(DX、OpenGL、OpenCL,甚至CUDA、Mantle这种专有API都可以监控),它都能准确地收集GPU的占用情况,另外由于两者是实际负责分配GPU资源的,位于驱动层面,它们回报数据的精准度也要比很多第三方工具要高,使得任务管理器有很高的精度。

既然有很高的精度,那它为什么还是报不准我的GPU占用率呢?这就牵扯到另一个问题,GPU引擎。

现代GPU上除了有主要用于图形、通用计算的统一计算单元外,还会集成一些其他的电路,比如说,用于视频编解码的专用模块。它们之间的关系一般是并行的,GPU可以同时运行图形计算和视频编码任务,在驱动层面,这些不同的模块就被抽象为不同的Engine,也就是引擎,比如说一个典型的GPU可以有以下这些引擎:

在具体执行任务的时候,不同的任务会在不同的引擎上面执行,比如说我打游戏,就用到3D引擎;我用显卡加速PremierePro,就用到CUDA引擎;我用NVENC编码视频,就用到视频编码引擎。

一张RTX2060显卡被系统抽象出的引擎

由于部分引擎之间有复用的关系,比如说3D引擎和CUDA引擎复用CUDACores进行计算,那么如果通过简单加法来计算占用率,那这个占用率就有可能会超过100%。开发团队也考虑过使用平均利用率来表示,但也不靠谱。那3D引擎不是被用的最多吗,就用它怎么样?也不太行,比如在视频引擎满载而3D引擎空载的情况下,它将会显示0%的占用率,也是不准确的。最终,开发团队选择将当前最为繁忙的引擎占用率作为GPU整体占用率的代表。

恩……博文说的很好,那么到今天为止这个功能上线也有一段时间了,其具体表现是怎样的呢?让我们看回顶上的那张图,在GPU的CUDA引擎满载的情况下,其左边的整体占用率仍然很低,显然是没有达到开发团队所说的。

我们又测试了一下别的情况,这里使用NVENC对视频进行编码,此时可以看到左边窗格中的GPU占用率又跑到了满载。

而在跑典型的3D应用程序的时候,它也很正常。

最后,我们尝试了OpenCL负载,这次任务管理器又能反映出CUDA引擎的占用率了。

如此看来,任务管理器GPU占用率的薛定谔情况可能是Windows10的一个Bug所导致的,在大部分情况下,它都会反映负载最大引擎的占用率,但在某些情况下,它并不能够正确地显示当前最繁忙引擎的占用情况。