锁页主机内存
虚拟内存:现代操作系统都支持虚拟内存,虚拟内存是通过将内存中暂时不使用的内容换出到外存上,从而腾出空间存放将要调入内存的内容。
分页存储管理方式:分页的基本方法是将地址空间等分成某一个固定大小的页,来方便管理。
锁页主机内存(Pinned Host Memory或Page-locked Host Memory):锁页就是将内存页面标记为不可被操作系统换出的内存,也就是不会被换出到磁盘中。锁页内存又可以称为固定内存、不可分页内存。
零拷贝
CPU内存管理默认是虚拟内存管理,也就是pageable(可分页),而GPU内存管理默认都是锁页的。
零拷贝(Zero Copy):通过设置CPU锁页内存,允许GPU通过物理地址直接访问CPU内存(Direct Memory Access,DMA),直接在主机和GPU之间复制数据。
原理:在内存和设备之间传输数据的时候,零拷贝可以避免从外存到内存的复制操作,从而提高数据传输效率。
对于可分页的CPU内存,会先分配锁页主机内存,再将需要的数据拷贝到这个锁页主机内存,然后从锁页内存传输数据给设备内存。因为对于可分页的CPU内存,可能会被CPU换出了内存,所以GPU需要拷贝该数据的时候并不能安全访问到。
但是对于已经锁页的CPU内存,GPU可以根据物理地址直接访问,Kernel就可以直接使用指针来访问主机内存了,读取的数据会直接写入寄存器中。
优势:
- 锁页内存的地址可以从主机地址空间映射到CUDA 地址空间,免去了从外存换入内存的拷贝开销,也就是实现零拷贝。
- 当设备内存不足的情况下可以利用主机内存。
- 提高PCIe传输率,因为GPU不需要先等CPU内容拷贝到GPU再进行运算,就可以进行流水线,也就边传输边计算。
零拷贝的使用情况
集成显卡和独立显卡:
- 集成显卡:集成显卡集成在主板上,通常和CPU集成在同一个芯片组,通常与系统内存共享一部分空间,对系统内存的性能产生一定的影响。
- 独立显卡:独立显卡是独立的图形处理器,插在主板上的PCIe插槽中,拥有自己的独立显存,可以很方便更新换代
PCIe总线:总线就是处理器连接多个硬件之间的一条电路,PCIe是个规范。
零拷贝的使用:
- 集成显卡:一定能提高性能,和CPU本身就共享内存,使用锁页主机内存后,能够避免外存拷贝到内存的开销。
- 独立显卡:
- 数据不适合GPU内存,例如数据太大GPU存不下。
- 该数据GPU只和CPU进行一次读写,如果需要进行多次读写的话会产生GPU和CPU数据多次搬运开销,不如一次性复制到GPU。
注意:锁页主机内存是稀缺资源,分配太多会使得虚拟内存的物理存储器数量减少,会降低系统的整体性能。
如何使用零拷贝
GPU内存默认都是锁业内存的,并不支持虚拟内存,也就是不支持把内存交换到磁盘上。
CPU内存默认都是可分页的,支持虚拟内存,也就是支持把内存交换到磁盘上。所以如果先要在主机上分配锁页内存,那么就不能用之常用的分配内存方式。而是需要用cudaMallocHost() 和cudaHostAlloc()来分配锁页主机内存,或者cudaHostRegister() 把可分页内存标记为锁页内存,从而实现零拷贝。
__host__ cudaError_t cudaMallocHost ( void** ptr, size_t size )
__host__ cudaError_t cudaHostAlloc ( void** pHost, size_t size, unsigned int flags )
__host__ cudaError_t cudaHostRegister(void *ptr, size_tsize, unsigned int flags )
其中cudaHostAlloc中的flags参数有四种:
- cudaHostAllocDefault:默认,等价于cudaMallocHost。
- cudaHostAllocPortable:多个CPU线程通过共享一块页锁定内存,从而实现cpu线程间的通信。
- cudaHostAllocWriteCombined:提升块锁页在PCIE总线(用于主机和外部设备的连接)上传输速率, 对主机来说没什么提升效率。对于主机写和设备读的情况来说,比较适合。
- cudaHostAllocMapped:分配的内存可以被kernel(核函数)直接访问。
而cudaHostRegister中的flags参数只有cudaHostAllocPortable和cudaHostAllocMapped。
注意,锁页主机内存需要用cudaFreeHost()来释放。
__host__ cudaError_t cudaFreeHost ( void* ptr )