前面说到了使用GPU加速文件搜索,但是会占用较多的显存,如果在打游戏,或者跑深度学习的情况下,并不会进行搜索,那么显存就被白白浪费掉了。因此如何实现显存的使用率监控,并实现检测到显存占用过多自动释放搜索缓存就成为了一个问题。
CUDA方面
在NVIDIA的文档中,有一个函数cudaMemGetInfo
1
| __host__cudaError_t cudaMemGetInfo ( size_t* free, size_t* total )
|
该方法可以直接获取GPU的空闲内存以及总内存。
但是经过实测,返回结果并不准确。因此并没有使用该方法。
OpenCL方面
在OpenCL中没有可以获取GPU占用的函数,但是由于OpenCL可以通过cl_device::getInfo获取显卡的各项参数,因此可以通过OpenCL提供的拓展参数来获取。
比如AMD显卡的OpenCL扩展中就有一项
1 2 3 4
| #define CL_DEVICE_GLOBAL_FREE_MEMORY_AMD 0x4039
cl_device::getInfo<CL_DEVICE_GLOBAL_FREE_MEMORY_AMD>();
|
AMD所有的OpenCL扩展在这里
https://registry.khronos.org/OpenCL/extensions/amd/cl_amd_device_attribute_query.txt
不过这样也有一些问题,比如适配困难。现在Intel也推出了自家的独立显卡Arc系列,不同显卡的适配就成为了一个问题。因此该方法也没有采用。
Windows任务管理器
其实在Windows任务管理器中就可以看到显存的占用,因此如何获取这个数值就成为了问题的关键。

在Windows的powershell中有这样的API
Get-Counter (Microsoft.PowerShell.Diagnostics) - PowerShell | Microsoft Learn
通过Get-Counter可以实现获取系统中的各项参数。比如我们这里需要获取GPU相关的信息

在Stack Overflow也找到了相同的解决方案
c++ - How to get the “Dedicated GPU memory” number for every running process in Windows (The same numbers that are shown in the Windows Task Manager) - Stack Overflow
由于powershell调用的效率太低,因此我们需要直接使用对应的Windows API,而不是重复调用powershell脚本。
也就是Windows API中的PDH函数
PDH函数
Using the PDH Functions to Consume Counter Data - Win32 apps | Microsoft Learn
通过PdhOpenQuery函数打开一个查询,然后使用PdhAddCounter设置要查询的信息,最后使用PdhCollectQueryData获取信息,再使用PdhGetRawCounterArray进行转换,即可获取我们想要的显存占用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| std::unordered_map<std::wstring, LONGLONG> query_pdh_val(PDH_STATUS& ret) { PDH_HQUERY query; std::unordered_map<std::wstring, LONGLONG> memory_usage_map; ret = PdhOpenQuery(nullptr, NULL, &query); if (ret != ERROR_SUCCESS) { return memory_usage_map; } PDH_HCOUNTER counter; ret = PdhAddCounter(query, L"\\GPU Adapter Memory(*)\\Dedicated Usage", NULL, &counter); if (ret != ERROR_SUCCESS) { return memory_usage_map; } ret = PdhCollectQueryData(query); if (ret != ERROR_SUCCESS) { return memory_usage_map; } DWORD bufferSize = 0; DWORD itemCount = 0; PdhGetRawCounterArray(counter, &bufferSize, &itemCount, nullptr); auto&& lpItemBuffer = reinterpret_cast<PPDH_RAW_COUNTER_ITEM_W>(new char[bufferSize]); ret = PdhGetRawCounterArray(counter, &bufferSize, &itemCount, lpItemBuffer); if (ret != ERROR_SUCCESS) { delete[] lpItemBuffer; return memory_usage_map; } for (DWORD i = 0; i < itemCount; ++i) { auto& [szName, RawValue] = lpItemBuffer[i]; memory_usage_map.insert(std::make_pair(szName, RawValue.FirstValue)); } delete[] lpItemBuffer; ret = PdhCloseQuery(query); return memory_usage_map; }
|
这里的query_pdh_val()方法返回的是GPU的luid和显存占用的map。
和powershell返回的结果是相同的

但是这里又出现了一个大问题,这些luid开头的东西是什么,和显卡是怎么进行对应的。
我们平时看到的显卡名都是像NVIDIA GeForce RTX 2060,AMD Radeon RX 5700XT这样

powershell中返回的gpu adapter memory(luid_0x00000000_0xXXXXXXXX)到底是如何和显卡一一对应的仍然不清楚。
GPU名称和luid的对应查询方案
在网上找了非常多的资料,都没有找到。最后是在翻看Windows DirectX API中偶然发现了在DXGI_ADAPTER_DESC结构体中有一个字段,名字就叫AdapterLuid
1 2 3 4 5 6 7 8 9 10 11
| typedef struct DXGI_ADAPTER_DESC { WCHAR Description[128]; UINT VendorId; UINT DeviceId; UINT SubSysId; UINT Revision; SIZE_T DedicatedVideoMemory; SIZE_T DedicatedSystemMemory; SIZE_T SharedSystemMemory; LUID AdapterLuid; } DXGI_ADAPTER_DESC;
|
因此,只需要通过DirectX API将显卡和luid对应起来,再和前面的PDH函数查询出的信息进行对应,即可实现显卡和显存占用信息的对应。
具体的方法为如下:
创建DXGIFactory,这里由于是jni调用的,所以我抛出了一个Java的异常。
1 2 3 4 5
| if (CreateDXGIFactory(__uuidof(IDXGIFactory), reinterpret_cast<void**>(&p_dxgi_factory)) != S_OK) { env->ThrowNew(env->FindClass("java/lang/Exception"), "Create dxgi factory failed."); return; }
|
然后使用DXGIFactory遍历所有GPU,即可获取GPU名称和luid的对应关系
1 2 3 4 5 6 7 8
| for (UINT i = 0; p_dxgi_factory->EnumAdapters(i, &p_adapter) != DXGI_ERROR_NOT_FOUND; ++i) { DXGI_ADAPTER_DESC adapter_desc; p_adapter->GetDesc(&adapter_desc); gpu_name_adapter_map.insert(std::make_pair(adapter_desc.Description, adapter_desc)); }
|
将对应关系保存至gpu_name_adapter_map中。
Luid有两个成员,一个是LowPart,一个是HighPart
1 2 3 4
| typedef struct _LUID { DWORD LowPart; LONG HighPart; } LUID, *PLUID;
|
在Windows系统中是高位在前,低位在后,因此通过下面的方法即可获取luid,n2hexstr()方法是数字转16进制字符串。
1 2 3 4 5 6 7 8 9 10 11
| template <typename I> std::string n2hexstr(I w, size_t hex_len = sizeof(I) << 1) { static const char* digits = "0123456789ABCDEF"; std::string rc(hex_len, '0'); for (size_t i = 0, j = (hex_len - 1) * 4; i < hex_len; ++i, j -= 4) rc[i] = digits[w >> j & 0x0f]; return rc; }
auto&& luid_str = "0x" + n2hexstr(adapter_luid.HighPart) + "_" + "0x" + n2hexstr(adapter_luid.LowPart);
|
在我这台电脑上,例如想监控NVIDIA GeForce RTX 2060的显卡的显存占用。先通过gpu_name_adapter_map找到对应的luid,为0x00000000_0x00010fcf,然后调用query_pdh_val()函数获取所有显卡的显存占用,最后通过luid找出对应的显存占用即可。
至此,对显卡显存占用的查询监控方案结束。