CUDA统一内存、零复制内存、锁页内存

CUDA 6.0以后,出现了:统一内存,unified memory,其具有以下特性:
1 其声明有以下两种方式:
a 使用关键字managed,可选的和device,一起使用,比如:device managed int ret[1000];
b 使用函数cudaMallocManaged():
int *ret;
cudaMallocManaged(&ret,1000*sizeof(int));
cudaFree(ret);

2 使用上面两种方式声明的内存,能够被cpu和gpu同时访问,不需要显式的在cpu和gpu之间使用cudaMemcpy()在cpu和gpu之间进行内存传输
3 cuda的managed内存和其zero-copy memory之间的区别在于:
a zero-copy分配的是CPU中的固定页内存(pinned memory in cpu system ),这种方式下,根据从哪里索引这个内存,其速度将更快或者更慢,零复制内存
实际上是一种特殊形式的内存映射,它允许你将主机内存直接映射到GPU内存空间上。
b 统一内存,将内存与执行空间分开,这样所有的数据访问都很快

cuda中锁页内存和零复制内存
锁页内存允许GPU上的DMA控制器请求主机传输,而不需要CPU主机处理器的参与
CPU仍然可以访问上述锁页内存,但是此内存是不能移动或换页到磁盘上的
在GPU上分配的内存默认都是锁页内存,这只是因为GPU不支持将内存交换到磁盘上
在主机上分配锁页内存有以下两种方式:
a 使用特殊的cudaHostAlloc函数,对用的释放内存使用cudaFreeHost函数进行内存释放
b 使用常规的malloc函数,然后将其注册为(cudaHostRegister)锁页内存,注册为锁页内存只是设置一些内部标志位以确保内存不被换出,并告诉CUDA驱动程序,该内存为锁页内存,可以直接使用而不需要使用临时缓冲区

使用锁页内存需要注意以下几点:
a 不能分配太多,太多的话会降低系统整体性能
b 锁页内存和显存之间的拷贝速度是6G/s,普通的内存和显存之间的拷贝速度是3G/s(显存之间的拷贝速度是30G/s,CPU之间的速度是10G/s)
c 使用cudaHostAlloc函数分配内存,其内的内容需要从普通内存拷贝到锁页内存中,因此会存在:这种拷贝会带来额外的CPU内存拷贝时间开销,CPU需要把数据从可分页内存拷贝到锁页,但是采用cudaHostRegister把普通内存改为锁页内存,则不会带来额外的cpu内存拷贝时间开销,因为cudaHostAlloc的做法是先分配锁页内存,这时里面是没有数据的,那么需要将一般的内存拷贝过来,而对于cudaHostRegister内存,他是之间就使用malloc分配好的,cudaHostRegister只是设置一些内部标志位以确保其不被换出,相当于只是更改了一些标志位,就不存在前面说的数据拷贝
d 在某些设备上,设备存储器和主机锁页存储器之间的数据拷贝和内核函数可以并发执行
e 在某些设备上,可以将主机的锁页内存映射到设备地址空间,减少主机和设备之间的数据拷贝,要访问数据的时候不是像上面那那样将数据拷贝过来,而是直接通过主机总线到主机上访问 ,使用cudaHostAlloc分配时传入cudaHostAllocMapped,或者使用cudaHostRegister时传入cudaHostRegisterMapped标签
f 默认情况下,锁页内存是可以缓存的。在使用cudaHostAlloc分配时传入cudaHostAllocWriteCombined标签,将其标定为写结合,这意味着该内存没有一级二级缓存,这样有利用主机写该内存,而如果主机读取的话,速度将会极其慢,所以这种情况下的内存应当只用于那些主机只写的存储器

锁页内存分配的内存,也有对应的三种形式:

cudaMemcpyToSymbol可以将数据从host拷贝到global,cudaMemcpy也是从host到>global,这种情况下二个函数有什么区别吗?
和各位大佬讨论一下后,和大家分享一下~

cudaMemcpyToSymbol也有将数据从host拷贝到global的功能,以前只用过这个函数拷贝constant memory。拷贝方式的不同是由目的内存申请的方式决定的。申请的是device内存,cudaMemcpyToSymbol拷贝就是从host拷贝到global。申请的是constant内存,cudaMemcpyToSymbol拷贝就是从host拷贝到constant memory。

CUDA中与内存相关的一些函数:
cudaMemcpyToSymbol(主要用于将数据从host拷贝到device的constant memory,但也可以用于将数据从host拷贝到device的global区)

cudamalloc分配,对应使用cudaFree释放,使用cudaMemcpy在设备和主机之间进行数据传输

cudaHostAlloc(核心是,在主机上分配锁页内存,对应的释放函数cudaFreeHost,opencv中的数据结构CudaMem是使用的该函数进行内存分配,其优势有:
d 在某些设备上,设备存储器和主机锁页存储器之间的数据拷贝和内核函数可以并发执行
e 在某些设备上,可以将主机的锁页内存映射到设备地址空间,减少主机和设备之间的数据拷贝,要访问数据的时候不是像上面那那样将数据拷贝过来,而是直接通过主机总线到主机上访问 ,使用cudaHostAlloc分配时传入cudaHostAllocMapped,或者使用cudaHostRegister时传入cudaHostRegisterMapped标签
f 默认情况下,锁页内存是可以缓存的。在使用cudaHostAlloc分配时传入cudaHostAllocWriteCombined标签,将其标定为写结合,这意味着该内存没有一级二级缓存,这样有利用主机写该内存,而如果主机读取的话,速度将会极其慢,所以这种情况下的内存应当只用于那些主机只写的存储器)

这里讲下个人对cudaMalloc和cudaHostAlloc的理解:
cudaHostAlloc个人认为主要是在主机和设备之间进行数据交互的时候使用效率会比cudaMalloc高,另外需要注意的是,cudaHostAlloc分配的是主机上的内存,设备通过某种方式进行访问,cudaMalloc分配的是设备上的内存,其需要把相应的内容使用cudaMemcpy将数据从主机拷贝到设备,cudaHostAlloc函数的第三个参数有三个可选枚举,分别是:
cudaHostAllocDefault:
cudaHostAllocMapped(对应到cudaHostRegister函数是cudaHostRegisterMapped):在该标志位下,将会分配一块可映射到设备地址空间的分页锁定主机存储器。这块存储器有两个地址,一个是主机存储器上的,一个是设备存储器上的,主机指针是cudaHostAlloc返回,设备指针通过cudaHostGetDevicePointer函数检索到,可以使用这个设备指针在内核中访问这块存储器,但是也有例外:
在支持统一虚拟地址空间的设备上,主机通过cudaHostAlloc和cudaMalloc分配的内存使用,单个虚拟地址空间,指针指向哪个存储空间(主机存储器或任意一个设备存储器),可以通过cudaPointerGetAttributes确定:
当在使用统一地址空间的设备间复制存储器时,cudaMemcpy*中的cudaMemcpyKind参数没有作用,可设置成cudaMemcpyDefault
通过cudaHostAlloc分配的存储器默认在使用统一地址空间的设备间是可分享的,cudaHostAlloc返回的指针可被在这些设备上的内核直接使用,而不需要cudaHostGetDevicePointer获得设备指针

cudaHostAllocWriteCombined:主要是去掉一级二级缓存,这样主机写数据的时候会很快,而主机读取的时候极慢,所以用于就是用处主机只写不读

cudaHostRegister:是得到锁页内存的另一种方式,和cudaHostAlloc的区别是,cudaHostRegister是将现有的内存标记成锁页内存
cudaMallocPitch(opencv中的GpuMat使用的该函数对图像进行内存分配)

注意的是,位于同一个Block中的线程才能实现通信,不同Block中的线程不能通过共享内存、同步进行通信,而应采用原子操作或主机介入。

  • 6
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CUDA共享内存是一种位于GPU上的高速存,可以用于在同一个线程块内的线程之间共享数据。使用共内存可以显著提内存访问的效,并且减少对全局内存访问次数。 以下使用CUDA共享内存的一般步: 1. 声明共内存:在GPU核函数中,可以使用`__shared__`关键字来声明共享内存。共享内存的大小需要在编译时确定,并且是所有线程块中的线程共享的。 ```cuda __shared__ float sharedData[SIZE]; ``` 2. 将数据从全局内存复制到共享内存:在GPU核函数中,使用线程块中的线程来将数据从全局内存复制到共享内存中。可以使用线程块索引和线程索引来确定数据的位置。 ```cuda int tid = threadIdx.x; int blockId = blockIdx.x; int index = blockId * blockDim.x + tid; sharedData[tid] = globalData[index]; ``` 3. 同步线程块:在将数据复制到共享内存后,需要使用`__syncthreads()`函数来同步线程块中的线程。这样可以确保所有线程都已经将数据复制到共享内存中。 ```cuda __syncthreads(); ``` 4. 使用共享内存:一旦所有线程都已经将数据复制到共享内存中,可以使用共享内存进行计算。由于共享内存位于GPU的高速缓存中,所以访问速度较快。 ```cuda sharedData[tid] += 1.0f; ``` 5. 将数据从共享内存复制回全局内存:在计算完成后,可以使用线程块中的线程将数据从共享内存复制回全局内存。 ```cuda globalData[index] = sharedData[tid]; ``` 需要注意的是,共享内存的大小是有限的,不同的GPU架构及型号都有不同的限制。因此,在使用共享内存时,需要确保不超过设备的限制,并且合理地利用共享内存,以提高性能。此外,需要注意线程同步的位置和使用方法,以避免数据竞争和错误的结果。 以上是使用CUDA共享内存的基本步骤,具体的实现方式会根据具体问题而有所不同。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值