Page-Locked内存
运行时提供的函数允许使用锁页(也称为固定)主机内存(与 malloc() 分配的常规可分页主机内存相反):
cudaHostAlloc()
和cudaFreeHost()
分配和释放锁页主机内存;cudaHostRegister()
将malloc()
分配的内存范围变为锁页内存(有关限制,请参阅参考手册)。
使用页面锁定的主机内存有几个好处:
- 锁页主机内存和设备内存之间的复制可以与异步并发执行中提到的某些设备的内核执行同时执行。
- 在某些设备上,锁页主机内存可以映射到设备的地址空间,从而无需将其复制到设备内存或从设备内存复制,如映射内存中所述。
- 在具有前端总线的系统上,如果主机内存被分配为页锁定,则主机内存和设备内存之间的带宽更高,如果另外分配为合并访存,则它甚至更高,如合并写入内存中所述。
然而,锁页主机内存是一种稀缺资源,因此锁页内存中的分配将在可分页内存中分配之前很久就开始失败。 此外,通过减少操作系统可用于分页的物理内存量,消耗过多的页面锁定内存会降低整体系统性能。
注意:页面锁定的主机内存不会缓存在非 I/O 一致的 Tegra 设备上。 此外,非 I/O 一致的 Tegra 设备不支持 cudaHostRegister()。
简单的零拷贝 CUDA 示例附带关于页面锁定内存 API 的详细文档。
Portable Memory
一块锁页内存可以与系统中的任何设备一起使用(有关多设备系统的更多详细信息,请参阅多设备系统),但默认情况下,使用上述锁页内存的好处只是与分配块时当前的设备一起可用(并且所有设备共享相同的统一地址空间,如果有,如统一虚拟地址空间中所述)。块需要通过将标志cudaHostAllocPortable
传递给cudaHostAlloc()
来分配,或者通过将标志cudaHostRegisterPortable
传递给cudaHostRegister()
来锁定页面。
写合并内存
默认情况下,锁页主机内存被分配为可缓存的。它可以选择分配为写组合,而不是通过将标志 cudaHostAllocWriteCombined
传递给 cudaHostAlloc()
。 写入组合内存释放了主机的 L1 和 L2 缓存资源,为应用程序的其余部分提供更多缓存。 此外,在通过 PCI Express 总线的传输过程中,写入组合内存不会被窥探,这可以将传输性能提高多达 40%。
从主机读取写组合内存非常慢,因此写组合内存通常应用于仅主机写入的内存。
应避免在 WC 内存上使用 CPU 原子指令,因为并非所有 CPU 实现都能保证该功能。
Mapped Memory
通过将标志 cudaHostAllocMapped
传递给 cudaHostAlloc()
或通过将标志 cudaHostRegisterMapped
传递给 cudaHostRegister()
,也可以将锁页主机内存块映射到设备的地址空间。因此,这样的块通常有两个地址:一个在主机内存中,由 cudaHostAlloc()
或 malloc()
返回,另一个在设备内存中,可以使用 cudaHostGetDevicePointer()
检索,然后用于从内核中访问该块。唯一的例外是使用 cudaHostAlloc()
分配的指针,以及统一虚拟地址空间中提到的主机和设备使用统一地址空间。
直接从内核中访问主机内存不会提供与设备内存相同的带宽,但确实有一些优势:
- 无需在设备内存中分配一个块,并在该块和主机内存中的块之间复制数据;数据传输是根据内核的需要隐式执行的;
- 无需使用流(请参阅并发数据传输)将数据传输与内核执行重叠;内核发起的数据传输自动与内核执行重叠。
然而,由于映射的锁页内存在主机和设备之间共享,因此应用程序必须使用流或事件同步内存访问(请参阅异步并发执行)以避免任何潜在的 read-after-write、write-after-read 或 write-after-write危险。
为了能够检索到任何映射的锁页内存的设备指针,必须在执行任何其他 CUDA 调用之前通过使用 cudaDeviceMapHost
标志调用 cudaSetDeviceFlags()
来启用页面锁定内存映射。否则, cudaHostGetDevicePointer()
将返回错误。
如果设备不支持映射的锁页主机内存,cudaHostGetDevicePointer()
也会返回错误。应用程序可以通过检查 canMapHostMemory
设备属性(请参阅[设备枚举](https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#device-enumeration)来查询此功能,对于支持映射锁页主机内存的设备,该属性等于 1。
请注意,从主机或其他设备的角度来看,在映射的锁页内存上运行的原子函数(请参阅原子函数)不是原子的。
另请注意,CUDA 运行时要求从主机和其他设备的角度来看,从设备启动到主机内存的 1 字节、2 字节、4 字节和 8 字节自然对齐的加载和存储保留为单一访问设备。在某些平台上,内存的原子操作可能会被硬件分解为单独的加载和存储操作。这些组件加载和存储操作对保留自然对齐的访问具有相同的要求。例如,CUDA 运行时不支持 PCI Express 总线拓扑,其中 PCI Express 桥将 8 字节自然对齐的写入拆分为设备和主机之间的两个 4 字节写入。