总体情况
一款对带宽、CPU使用率和GPU使用率要求非常高的应用程序需要从一个GPU传输每秒约10-15GB到另一个GPU。它使用DX11 API访问GPU,所以上传至GPU只能使用需要为每个单独上传进行映射的缓冲区。上传每次以25MB的块进行,并发地有16个线程正在同时写入缓冲区。在这方面无法做太多改变。如果没有以下bug,实际并发写入的级别应该会更低。
这是一台配备了3个Pascal GPU、高端Haswell处理器和四通道RAM的强大工作站。硬件上几乎不能再改进了。它运行着Windows 10桌面版。
实际问题
一旦CPU负载超过50%,位于MmPageFault()中(Windows内核内部,在访问已映射到您的地址空间但尚未被操作系统提交的内存时调用)的某些东西会出现严重错误,并且剩余的50% CPU负载将浪费在MmPageFault()内部的自旋锁上。CPU利用率达到100%,应用程序性能完全下降。
我必须假设这是由于每秒需要分配给进程的大量内存,每次DX11缓冲区未映射时完全取消映射。相应地,因为memcpy()
是按顺序写入缓冲区,所以它实际上是每秒数千个对于每个单个未提交的页面发生的MmPageFault()调用。
一旦CPU负载超过50%,Windows内核中保护页面管理的乐观自旋锁在性能方面完全下降。
考虑因素
缓冲区由DX11驱动程序分配。无法调整分配策略。不能使用不同的内存API,特别是无法重复使用。
DX11 API(映射/取消映射缓冲区)的所有调用都来自单个线程。实际的复制操作可能在比系统中的虚拟处理器更多的线程上进行。
减少内存带宽要求是不可能的。这是一个实时应用程序。事实上,硬限制目前是主GPU的PCIe 3.0 16x带宽。如果可以的话,我已经需要推得更远了。
避免多线程复制是不可能的,因为有独立的生产者-消费者队列,不能轻松合并。
自旋锁性能退化似乎非常罕见(因为使用情况被推得如此之远),在Google上,您找不到一个单独的自旋锁函数的结果。
升级到提供更多映射控制的API(Vulkan)正在进行中,但它不适合作为短期修复。出于同样的原因,目前不能切换到更好的操作系统内核。
减少CPU负载也行不通;除了(通常微不足道且廉价的)缓冲区复制之外,还有太多需要完成的工作。
问题
有什么办法吗?
我需要大幅减少单独页面故
// In the processing threads
{
DX11DeferredContext->Map(..., &buffer)
std::memcpy(buffer, source, size);
DX11DeferredContext->Unmap(...);
}