3.2.5 分页锁定主机存储器
运行时提供了分配和释放分页锁定主机存储器(也称为pinned)的函数cudaHostAlloc()和cudaFreeHost(),分页锁定主机存储器与常规的使用malloc()分配的可分页的主机存储器不同。
使用分页锁定主机存储器有许多优点:
1、如3.2.6节提到的,在某些设备上,设备存储器和分页锁定主机存储器间数据拷贝可与内核执行并发进行;
2、在一些设备上,分页锁定主机内存可映射到设备地址空间,减少了和设备间的数据拷贝,详见3.2.5.3节;
3、在有前端总线的系统上,如果主机存储器是分页锁定的,主机存储器和设备存储器间的带宽会高些,如果再加上3.2.5.2节所描述的写结合(write-combining)的话,带宽会更高。
然而分页锁定主机存储器是稀缺资源,所以可分页内存分配得多的话,分配会失败。另外由于减少了系统可分页的物理存储器数量,分配太多的分页锁定内存会降低系统的整体性能。
SDK中的simple zero-copy例子中有分页锁定API的详细文档。
3.2.5.1可分享存储器(portable memory)
一块分页锁定存储器可被任何主机线程使用,但是默认的情况下,只有分配它的线程可以使用它。为了让所有线程可以使用它,可以在使用cudaHostAlloc()分配时传入cudaHostAllocPortable标签。
3.2.5.2 写结合存储器
默认情况下,分页锁定主机存储器是可缓存的。可以在使用cudaHostAlloc()分配时传入cudaHostAllocWriteCombined标签使其被分配为写结合的。写结合存储器没有一级和二级缓存资源,所以应用的其它部分就有更多的缓存可用。另外写结合存储器在通过PCI-e总线传输时不会被监视(snoop),这能够获得高达40%的传输加速。
从主机读取写结合存储器极其慢,所以写结合存储器应当只用于那些主机只写的存储器。
3.2.5.3 被映射存储器
在一些设备上,在使用cudaHostAlloc()分配时传入cudaHostAllocMapped标签可分配一块被映射到设备地址空间的分页锁定主机存储器。这块存储器有两个地址:一个在主机存储器上,一个在设备存储器上。主机指针是从cudaHostAlloc()返回的,设备指针可通过cudaHostGetDevicePointer()函数检索到,可以使用这个设备指针在内核中访问这块存储器。
从内核中直接访问主机存储器有许多优点:
a. 无须在设备上分配存储器,也不用在这块存储器和主机存储器间显式传输数据;数据传输是在内核需要的时候隐式进行的。
b. 无须使用流(参见3.2.6.4节)重叠数据传输和内核执行;数据传输和内核执行自动重叠。
由于被映射分页锁定存储器在主机和设备间共享,应用必须使用流或事件(参见3.2.6节)来同步存储器访问以避免任何潜在的读后写,写后读,或写后写危害。
一块分页锁定存储器可同时分配为被映射的和可分享的(见3.2.5.1节),这种情况下,每个要映射这块存储器的主机线程必须调用cudaHostGetDevicePointer()检索设备指针,因为每个主机线程持有的设备指针一般不同。
为了在给定的主机线程中能够检索到被映射分页锁定存储器的设备指针,必须在调用任何CUDA运行时函数前调用cudaSetDeviceFlags(),并传入cudaDeviceMapHost标签。否则,cudaHostGetDevicePointer()将会返回错误。
如果设备不支持被映射分页锁定存储器,cudaHostGetDevicePointer()将会返回错误。
应用可能会查询设备是否支持映射分页锁定主机存储器,可以使用函数cudaGetDeviceProperties()检查canMapHostMemory属性。
注意:从主机和其它设备的角度看,操作被映射分页锁定存储器的原子函数(5.4.3和B.10节)不是原子的。