【转载】 CUDA中的Unified Memory
为了结合上篇 文章 https://www.cnblogs.com/devilmaycry812839668/p/13264080.html
对RTX显卡是否能够实现P2P通信功能,同时专业级别显卡的共享内存功能是需要应用程序依托NVLINK高速访存的特性进行硬编码实现的很不是nvidia显卡或驱动原生具有的特性(这是个未验证的猜想),等,进行进一步的研究,转载下文:
转载自 知乎:
https://zhuanlan.zhihu.com/p/82651065
-------------------------------------------------------------------------

CUDA中的Unified Memory
作者:
在CUDA 6中,NVIDIA引入了CUDA历史上一个最重要的一个编程模型改进之一,unified memory(以下简称UM)。在今天典型的PC上,CPU与GPU的内存是物理上独立的,通过PCI-E总线进行连接通信。实际上,在CUDA 6.0之前,程序员必须在编程期间很清楚这一点,并且反应在代码中。必须在CPU和GPU两端都进行内存分配,并不断地进行手动copy,来保证两端的内存一致。
Unified memory在程序员的视角中,维护了一个统一的内存池,在CPU与GPU中共享。使用了单一指针进行托管内存,由系统来自动地进行内存迁移。
OK, talk is cheap, show me the code.
首先以排序文件中的数据来举例,简单对比一下CPU代码(左)与带有UM的CUDA代码(右):
void sortfile(FILE *fp, int N) void sortfile(FILE *fp, int N)
{ {
char *data; char *data;
data = (char*)malloc(N); cudaMallocManaged(data, N); fread(data, 1, N, fp); fread(data, 1, N, fp); qsort(data, N, 1, compare); qsort<<<...>>>(data, N, 1, compare);
cudaDeviceSynchronize(); usedata(data); usedata(data);
free(data); free(data);
} }
可以清晰地看到,两段代码惊人地相似。
仅有的不同在于:
- GPU版本
 - 使用cudaMallocManaged来分配内存,而非malloc
 - 由于CPU与GPU间是异步执行,因此在launch kernel后需要调用cudaDeviceSynchronize进行同步。
 
在CUDA 6.0之前,要实现以上的功能,可能需要以下的代码:
void sortfile(FILE *fp, int N)
{
char *h_data, *d_data;
h_data= (char*)malloc(N);
cudaMalloc(&d_data, N); fread(h_data, 1, N, fp); cudaMemcpy(d_data, h_data, N, cudaMemcpyHostToDevice); qsort<<<...>>>(data, N, 1, compare); cudaMemcpy(h_data, h_data, N, cudaMemcpyDeviceToHost); //不需要手动进行同步,该函数内部会在传输数据前进行同步 usedata(data);
free(data);
}
到目前为止,可以看出主要有以下的优势:
- 简化了代码编写和内存模型
 - 可以在CPU端和GPU端共用一个指针,不用单独各自分配空间。方便管理,减少了代码量。
 - 语言结合更紧密,减少与兼容语言的语法差异。
 - 更方便的代码迁移。
 
Deep Copy
等等。。。从之前的描述来看,好像也并没有减少很多代码量。。那我们接下来考虑一个十分常见的情况,。当我们拥有一个这样的结构体:
struct dataElem {
    int data1;
    int data2;
    char *text;
}
我们可能要进行这样的处理:
void launch(dataElem *elem)
{
dataElem *d_elem;
char *d_text; int textlen = strlen(elem->text); // 在GPU端为g_elem分配空间
cudaMalloc(&d_elem, sizeof(dataElem));
cudaMalloc(&d_text, textlen);
// 将数据拷贝到CPU端
cudaMemcpy(d_elem, elem, sizeof(dataElem));
cudaMemcpy(d_text, elem->text, textlen);
// 根据gpu端分配的新text空间,更新gpu端的text指针
cudaMemcpy(&(d_elem->text), &d_text, sizeof(g_text)); // 最终CPU和GPU端拥有不同的elem的拷贝
kernel<<< ... >>>(g_elem);
}
但在CUDA 6.0后,由于UM的引用,可以这样:
void launch(dataElem *elem)
{
kernel<<< ... >>>(elem);
}
很明显,deep copy的情况下,UM极大地减少了代码量。在UM出现之前,由于两端地址空间不同步,需要进行多次的手动分配和拷贝内存。尤其对于非cuda程序员,十分不习惯,而且十分繁琐。当实际数据结构更加复杂时,两者差距会更加显著。
而对于常见数据结构--链表,本质上是有指针组成的嵌套的数据结构,在没有UM的情况下, CPU与GPU间共享链表非常难以处理,内存空间的传递很复杂。
这时使用UM,可以有以下的优势:
- 在CPU和GPU间直接传递链表元素。
 - 在CPU或GPU任一一端来修改链表元素。
 - 避免了复杂的同步问题。
 
不过实际上在UM出现前,可以 Zero-copy memory(pinned host memory)来解决这个复杂的问题。但即使这样,UM的存在仍有意义,因为pinned host memory的数据获取受制于PCI-express的性能,使用UM可以获得更好的性能。对于这一问题,本文暂时不进行深入讨论。
Unified Memory with C++
由于现代C++中尽量避免显式调用malloc等内存分配函数,而使用new来进行wrap。因此可以通过override new函数来使用UM。
class Managed {
    void *operator new(size_t len)
   {
        void *ptr;
         cudaMallocManaged(&ptr, len);
         return ptr;
   }
    void operator delete(void *ptr)
   {
        cudaFree(ptr);
   }
};
通过继承该class,从而让custom C++ class实现UM的pass-by-reference。而通过在constructor中,调用cudaMallocManaged来实现UM下的pass-by-value。下面以一个string class来说明:
// 通过继承来实现 pass-by-reference
class String : public Managed {
int length;
char *data;
// 通过copy constructor实现pass-by-value
String (const String &s) {
length = s.length;
cudaMallocManaged(&data, length);
memcpy(data, s.data, length);
}
};
Unified Memory or Unified Virtual Addressing?
实际上在CUDA 4.0就开始支持Unified Virtual Addressing了,请不要与Unified Memory混淆。尽管UM是依赖于UVA的,但实际上他们并不是一回事。要讲清楚这一个问题,首先我们要知道UVA所关心的内存类型
- device memory (可能在不同的gpu上)
 - on-chip shared memory
 - host memory
 
而对于SM中的local memory,register等线程相关的内存,很明显不在UVA所关注的范围。因此UVA实际上是为这些内存提供统一的地址空间,为此UVA启用了zero-copy技术,在CPU端分配内存,将CUDA VA映射上去,通过PCI-E进行每个操作。而且注意,UVA永远不会为你进行内存迁移。
关于两者之间更深入的对比,和性能分析,超出了本文讨论范围,也许会在后续的文章中继续讨论。
疑问:
- 问:UM会消除System Memory和GPU Memory之间的拷贝么?
 - 答:不会,只是这部分copy工作交给CUDA在runtime期间执行,只是对程序员透明而已。memory copy的overhead依然存在,还有race conditions的问题依然需要考虑,从而确保GPU与CPU端的数据一致。简单的说,如果你手动管理内存能力优秀,UM不可能为你带来更好的性能,只是减少你的工作量。
 - 问:既然并没有消除数据间的拷贝,看起来这只是compiler time的事情,为什么仍需要算力3.0以上?难道是为了骗大家买卡么?
 - wait....到目前为止,实际上我们省略了很多实现细节。因为原则上不可能消除拷贝,无法在compile期间获知所有消息。而且重要的一点,在Pascal以后的GPU架构中,提供了49-bit的虚拟内存寻址,和按需页迁移的功能。49位寻址长度足够GPU来cover整个sytem memory和所有的GPUmemory。而页迁移引擎通过内存将任意的可寻址范围内的内存迁移到GPU内存中,来使得GPU线程可以访问non-resident memory。
 - 简言之,新架构的卡物理上允许GPU访问”超额“的内存,不用通过修改程序代码,就使得GPU可以处理out-of-core运算(也就是待处理数据超过本地物理内存的运算)。
 - 而且在Pascal和Volta上甚至支持系统范围的原子内存操作,可以跨越多个GPU,在multi-GPU下,可以极大地简化代码复杂度。
 - 同时,对于数据分散的程序,按需页迁移功能可以通过page fault更小粒度地加载内存,而非加载整个内存,节约更多数据迁移的成本。(其实CPU很早就有类似的事情,原理很相似。)
 
【转载】 CUDA中的Unified Memory的更多相关文章
- CUDA02 - 访存优化和Unified Memory
		
CUDA02 - 的内存调度与优化 前面一篇(传送门)简单介绍了CUDA的底层架构和一些线程调度方面的问题,但这只是整个CUDA的第一步,下一个问题在于数据的访存:包括数据以何种形式在CPU/GPU之 ...
 - CUDA中并行规约(Parallel Reduction)的优化
		
转自: http://hackecho.com/2013/04/cuda-parallel-reduction/ Parallel Reduction是NVIDIA-CUDA自带的例子,也几乎是所有C ...
 - cuda中时间用法
		
转载:http://blog.csdn.net/jdhanhua/article/details/4843653 在CUDA中统计运算时间,大致有三种方法: <1>使用cutil.h中的函 ...
 - OpenCV二维Mat数组(二级指针)在CUDA中的使用
		
CUDA用于并行计算非常方便,但是GPU与CPU之间的交互,比如传递参数等相对麻烦一些.在写CUDA核函数的时候形参往往会有很多个,动辄达到10-20个,如果能够在CPU中提前把数据组织好,比如使用二 ...
 - cuda中模板的使用
		
模板是C++的一个重要特征,它可以让我们简化代码,同时使代码更整洁.CUDA中也支持模板,这给我们编写cuda程序带来了方便.不过cuda4.0之前和之后使用模板的方法不一样,这给我们带来了少许困难. ...
 - CUDA中多维数组以及多维纹理内存的使用
		
纹理存储器(texture memory)是一种只读存储器,由GPU用于纹理渲染的图形专用单元发展而来,因此也提供了一些特殊功能.纹理存储器中的数据位于显存,但可以通过纹理缓存加速读取.在纹理存储器中 ...
 - CUDA中使用多维数组
		
今天想起一个问题,看到的绝大多数CUDA代码都是使用的一维数组,是否可以在CUDA中使用一维数组,这是一个问题,想了各种问题,各种被77的错误状态码和段错误折磨,最后发现有一个cudaMallocMa ...
 - 6G显卡显存不足出现CUDA Error:out of memory解决办法
		
 从6月初开始,6G显存的显卡开始出现CUDA Error:out of memory的问题,这是因为dag文件一直在增加,不过要增加到6G还需要最少两年的时间. 现在出现问题的原因是1.内核太古老 ...
 - SCOPE 中 SPFILE、MEMORY、BOTH 的小小区别
		
ALTER SYSTEM 中 SCOPE=SPFILE/MEMORY/BOTH 的区别: SCOPE = SPFILE The change is applied in theserverparame ...
 - 【Linux】Linux中Swap与Memory内存简单介绍
		
背景介绍 对于Linux来说,其在服务器市场的使用已经占据了绝对的霸主地位,不可动摇.Linux的各种设计思想和使用也被传承(当然不乏各种黑Linux,而且黑的漂亮).Linux的很多独特的设计,对性 ...
 
随机推荐
- WIn32 C++ 消息处理函数 问题
			
这个消息处理这个 Winproc 这个 接收到网络信息 在自己的函数用完后可以选择向系统路由传递这个网络消息接收到的数据原型 你处理完,系统也处理,不想让系统处理可以不将接受到的那几个变量啊数据啊,就 ...
 - koishi机器docker搭建
			
硬件要求: 可用内存:1G以上 存储空间:1G以上 cpu:不限制 配置: 在docker的存储空间目录建立koishi文件夹 下载docker镜像 koishijs/koishi 建立容器,具体设置 ...
 - Java实现延迟执行代码
			
Java实现延迟执行代码对于Java程序在它们的操作中添加延迟或暂停是比较常见的.这对于任务暂停直到另外任务完成执行场景比较有用.本文我们提供两类方法实现延迟执行. 1. 基于线程(Thread)方法 ...
 - 妙用OSGraph:发掘GitHub知识图谱上的开源故事
			
1. 何为OSGraph? OSGraph (Open Source Graph) 是一个开源图谱关系洞察工具,基于GitHub开源数据全域图谱,实现开发者行为.项目社区生态的分析洞察.可以为开发者. ...
 - 抖音验证签名和接口含中文签名,需要在发送端加上utf8编码
			
抖音验证签名和接口含中文签名,需要在发送端加上utf8编码 抖音验签和抖音异步通知回调验签解决:是对整个接收的字符串做验签,而不是部分数据做验签解决中文参数问题,否则中文乱码报验签错误 签名算法htt ...
 - 使用嵌套的ScriptableObject及ReorderableList创建习题持久化数据
			
使用嵌套的ScriptableObject及ReorderableList创建习题持久化数据 效果展示 题集持久化数据:存储题目,可以直接在inspector面板上创建对应的问题子项 问题持久化数据 ...
 - Ansible的常用模块
			
目录 ansible常用模块 1. file模块 1.1 file模块的选项 1.2 file模块的使用 1.2.1 使用file模块在远程主机创建文件 1.2.2 创建目录 1.2.3 删除文件/目 ...
 - Linux/Unix-stty命令详解
			
文章目录 介绍 stty命令的使用方法 stty的参数 我常用的选项 所有选项 介绍 stty用于查询和设置当前终端的配置. 如果你的终端回车不换行.输入命令不显示等各种奇葩问题,那么stty命令可以 ...
 - 01-前端开发Vscode插件配置
			
01 自动保存配置 02 空格渲染方式 配置好以后,可以看到代码的空格有几个,以点的方式呈现,1个点表示1个空格 03 图标插件 VSCode Great Icons 04 缩进 推荐使用2 05 v ...
 - VSCode 中 Markdown Preview Enhanced 插件利用 Chrome (Puppeteer) 导出 PDF 文件使用说明与问题解决
			
准备 预先安装好 Chrome 浏览器. 使用方法 右键选择 Chrome (Puppeteer). 设置 Puppeteer 通过 front-matter 即在 markdown 文档开头加上 y ...