当前位置:网站首页>CUDA_主机内存

CUDA_主机内存

2020-11-09 23:48:31 一介草民李八千



主机内存

系统中被CPU访问的内存,分为两种类型:可分页内存(pageable memory,一般应用中默认使用)和页锁定内存(page-locked或者pinned)。

可分页内存即为通过操作系统api(malloc(), new())分配的存储器空间;而页锁定内存始终不会被分配到低速的虚拟内存中,能够保证存在于物理内存中,并且能够通过DMA加速与设备端的通信。

为了让硬件使用DMA,操作系统允许主机内存进行页锁定,并且因为性能原因,CUDA包含了开发者使用这些操作系统工具的API。页锁定后的且映射为cuda直接访问的锁定的内存允许以下几点:

  • 更快的传输性能;
  • 异步的复制操作(在必要的内存复制结束之前内存复制返回控制给调用者;GPU复制操作与cpu并行的执行);
  • 映射锁页内存可以被cuda内核直接访问

主机端锁页内存

使用pinned memory有很多好处:可以达到更高的主机-设备端的数据传送带宽,如果页锁定内存以write-commbined方式分配,带宽更高;某些设备支持DMA功能,在执行内核函数的同时利用pinned memory进行主机端与设备端之间的通信;在某些设备上,pinned memory还可以通过zero-copy功能映射到设备地址空间,从GPU直接访问,这样就不用在主存和显存之间进行数据传输。

分配锁页内存

通过cudaHostAlloc()和cudaFreeHost()来分配和释放pinned memory。


portable memory/可共享锁页内存

在使用cudaHostAlloc分配页锁定内存时,加上cudaHostAllocPortable标志,可以使多个CPU线程通过共享一块页锁定内存,从而实现cpu线程间的通信。在默认情况下,pinned memory由哪个cpu线程分配,就只有该CPU线程才能访问这块空间。而通过portable memory则可以让控制不同GPU的几个CPU线程共享同一块pinned memory,减少CPU线程间的数据传输和通信。


write-combined memory/写结合锁页内存

当CPU对一块内存中的数据进行处理时,会将这块内存上的数据缓存到CPU的L1、L2 Cache中,并且需要监视这块内存中数据的更改,以保证缓存的一致性。

一般情况下,这种机制可以减少CPU对内存的访问,但在“CPU生产数据,GPU消费数据”模型中,CPU只需要写这块内存数据即可。此时不需要维护缓存一致性,对内存的监视反而会降低性能。

通过write-combined memory,就可以不使用CPU的L1、L2 Cache对一块pinned memory中的数据进行缓冲,而将Cache资源留给其他程序使用。

write-combined memory在PCI-e总线传输期间不会被来自CPU的监视打断,可以将主机端-设备端传输带宽提高多达40%。

在调用cudaHostAlloc()时加上cudaHostAllocWriteCombined标志,就可以将一块pinned memory声明为write-combined memory.

由于对write-combined memory的访问没有缓存,CPU从write-combined memory上读数据时速度会有所下降。因最好只将CPU端只写的数据存放在write-combined memory中。


mapped memory/映射锁页内存

mapped memory拥有两个地址:主机端地址(内存地址)和设备地址(显存地址),可以在kernel中直接访问mapped memory中的数据,而不必再在内存和显存间进行数据拷贝,即zero-copy功能。如果内核程序只需要对mapped memory进行少量读写,这样做可以减少分配显存和数据拷贝的时间。

mapped memory在主机端的指针可以由cudaHostAlloc()函数获得;它的设备端指针可以通过cudaHostGetDevicePointer()获得,从kernel中访问页锁定内存,需要将设备端指针作为参数传入。

并不是所有的设备都支持内存映射,通过cudaGetDeviceProperties()函数返回的cudaMapHostMemory属性,可以知道当前设备是否支持mapped memory。如果设备提供支持,可以在调用cudaHostAlloc()时加上cudaHostAllocMapped标志,将pinned memory映射到设备地址空间。

由于mapped memory可以在CPU端和GPU端访问,所以必须通过同步来保证CPU和GPU对同一块存储器操作的顺序一致性。可以使用流和事件来防止读后写,写后读,以及写后写等错误。

对mapped memory的访问应该满足与global memory相同的合并访问要求,以获得最佳性能。

注意:

  • 在执行cuda操作之前,先调用cudaSetDeviceFlags()(加cudaDeviceMapHost标志)进行锁页内存映射。否则,调用cudaHostGetDevicePointer()函数会返回一个错误。
  • 多个主机端线程操作的一块portable memory同时也是mapped memory时,每一个主机线程都必须调用cudaHostGetDevicePointer()获得这一块pinned memory的设备端指针。此时,这一块内存在每个主机端线程中都有一个设备端指针。

注册锁页内存

锁页内存注册将内存分配与页锁定和主机内存映射分离。可以实现操作一个已分配的虚拟地址范围,并页锁定它。然后,将其映射给GPU,正如cudaHostAlloc()可以根据需要让内存映射到cuda地址空间或变成可共享的(所有GPU可访问)。

函数cuMemHostRegister()/cudaHostRegister()cuMemHostUnregister()/cudaHostUnregister()分别实现吧主机内存注册为锁页内存和移除注册的功能。

注册的内存范围必须是页对齐的;无论是基地址还是大小,都必须是可被操作系统页面大小整除。

注意:

当UVA(同一虚拟寻址)有效,所有的锁页内存分配均是映射的和可共享的。这一规则的例外是写结合内存和注册的内存。对于这二者,设备指针可能不同于主机指针,应用程序需要使用cudaHostGetDevicePointer()/cuMemHostGetDevicePointer()查询设备指针。

版权声明
本文为[一介草民李八千]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/TonvyLeeBlogs/p/13951385.html