《CUDA编程:基础与实践》读书笔记(2):CUDA内存
1. 全局内存
核函数中的所有线程都能够访问全局内存(global memory)。全局内存的容量是所有设备内存中最大的,但由于它没有放在GPU芯片内部,因此具有相对较高的延迟和较低的访问速度,cudaMalloc
分配的就是全局内存。此外,当处理逻辑上的二维或者三维问题时,还可以使用cudaMallocPitch
和cudaMalloc3D
分配内存,用cudaMemcpy2D
和cudaMemcpy3D
复制数据,释放时依然使用cudaFree
函数。
除了上述动态分配的全局内存外,CUDA也允许使用静态全局内存,其所占内存数量是在编译期确定的。静态全局内存变量必须在所有主机与设备函数外部定义,从其定义之处开始对一个翻译单元内的所有设备函数直接可见。
__device__ T x; //单个变量
__device__ T y[N]; //固定长度的数组
无需将静态全局内存变量传入核函数,核函数可以直接访问静态全局内存。不可以在主机函数中直接访问静态全局内存,但可以用如下函数在主机内存和静态全局内存之间传输数据:
cudaError_t cudaMemcpyToSymbol
(
const void* symbol, //静态全局内存变量
const void* src, //主机内存指针
size_t count, //复制的字节数
size_t offset = 0, //从symbol对应设备地址开始偏移的字节数
enum cudaMemcpyKind kind = cudaMemcpyHostToDevice
);
cudaError_t cudaMemcpyFromSymbol
(
void* dst, //主机内存指针
const void* symbol, //静态全局内存变量
size_t count, //复制的字节数
size_t offset = 0, //从symbol对应设备地址开始偏移的字节数
enum cudaMemcpyKind kind = cudaMemcpyDeviceToHost
);
一般来说,对全局内存的访问都会通过缓存:如果通过L1缓存, 那么内存访问由一个128字节的内存事务(memory transaction)实现;如果只通过L2缓存, 那么内存访问由一个32字节的内存事务实现。以L2缓存为例,在一次内存事务的数据传输中,从全局内存转移到L2缓存的一片内存的首地址一定是其最小粒度(32字节)的整数倍,也就是说,一次数据传输只能从全局内存读取地址为0~31字节、32~63字节、64~95字节、96~127字节等片段的数据。如果线程束请求的全局内存数据地址刚好为0~127,那么就能与4次数据传输所处理的数据完全吻合,这种情况下的访问是合并的(coalesced)。合并的内存访问不会浪费传输的数据,从而能够更好地利用显存带宽。
//cudaMalloc及其它CUDA运行时API分配的内存的首地址至少是256的整数倍。
//下面的例子中,假设x,y,z是由cudaMalloc分配的全局内存指针。
//第一个线程块中的线程束将访问数组x中的第0~31个元素,对应128字节的连续内存,且首地址一定是256字节的整数倍。这样的访问只需要4次数据传输即可完成,所以是合并访问,合并度为100%。
void __global__ add(float* x, float* y, float* z)
{
int n = threadIdx.x + blockIdx.x * blockDim.x;
z[n] = x[n] + y[n];
}
add<<<128, 32>>>(x, y, z);
//第一个线程块中的线程束将访问数组x中的第1~32个元素,假设数组x首地址为256字节,该线程束将访问设备内存的260~387字节,这将触发5次数据传输,对应的内存地址分别是256~287字节、288~319字节、320~351字节、352~383字节、384~415字节。这样的访问属于非合并的访问,合并度=使用的数据量/传输的数据量=256/320=80%。
void __global__ add(float* x, float* y, float* z)
{
int n = threadIdx.x + blockIdx.x * blockDim.x + 1;
z[n] = x[n] + y[n];
}
add<<<128, 32>>>(x, y, z);
2. 常量内存
常量内存(constant memory)是有常量缓存的全局内存,它可读不可写,且数量有限(仅有64KB)。使用常量内存的方法是在核函数外用__constant__
定义变量(可以是结构体),并且用cudaMemcpyToSymbol
函数将数据从主机端复制到设备的常量内存后供核函数使用。给核函数传递的参数(传值,不是像全局变量那样传递指针)就存放在常量内存中,但给核函数传递参数最多只能在一个核函数中使用4KB常量内存。
3. 纹理内存和表面内存
纹理内存(texture memory)和表面内存(surface memory)也是一种具有缓存的全局内存,一般仅可读(表面内存也可写)。
4. 寄存器
寄存器(register)变量仅对一个线程可见,它是所有内存中访问速度最高的。在核函数中定义的不加任何限定符的变量一般来说就存放在寄存器中。在核函数中定义的不加任何限定符的数组有可能存放在寄存器中、也有可能存放在局部内存中。各种内建变量,例如gridDim
、blockDim
、blockIdx
、threadIdx
以及warpSize
都保存在特殊的寄存器中。
5. 局部内存
寄存器中放不下的变量,以及索引值不能在编译时就确定的数组,都可能放在局部内存(local memory)中,这种判断是由编译器自动做的。虽然局部内存在用法上与寄存器类似,但从硬件上看,局部内存只是全局内存的一部分。所以,局部内存的延迟也较高,每个线程最多能使用512KB的局部内存,但过多使用也会降低程序性能。
6. 共享内存
共享内存(shared memory)具有仅次于寄存器的读写速度,数量也有限,它对整个线程块可见。在核函数中,如果要将一个变量定义为共享内存变量,就要在定义语句中加上限定符__shared__
,例如:
__shared__ float y[128];
除了上述的静态共享内存外,还可以使用动态共享内存,这不会影响程序性能,但有时可以提高程序的可维护性:
__global__ void func()
{
//使用动态共享内存,必须加上限定词extern,且不能指定数组大小
extern __shared__ float y[];
//...
}
//调用核函数的执行配置中,第三个参数指明动态共享内存的字节数
func<<<128, 32, sizeof(float) * 128>>>();
为了获得高的内存带宽,共享内存在物理上被分为32个bank,每个bank的宽度是4字节(只有Kepler架构的是8字节)。对于一个长度为128的单精度浮点数的共享内存数组来说,第0~31个数组元素依次对应到32个bank的第一层,第32~63个数组元素依次对应到32个bank的第二层,第64~95个数组元素依次对应到32个bank的第三层,第96~127个数组元素依次对应到32个bank的第四层。也就是说,每个bank分摊4个在地址上相差128字节的数据。
不同bank间的数据可以并行读写,而同一个bank中不同层的数据只能串行读写。所以,使用共享内存时要尽量避免同一个线程束内的多个线程访问同一个bank中不同层的数据,因为这会导致bank冲突,从而降低程序性能。
7. L1/L2缓存
从Fermi架构开始,有了SM层级的L1缓存和设备层级的L2缓存。它们主要用来缓存全局内存和局部内存的访问,减少延迟。在启用了L1缓存的情况下,对全局内存的读取将首先尝试经过L1缓存,如果未命中,则接着尝试经过L2缓存,如果再次未命中,则直接从全局内存读取。
《CUDA编程:基础与实践》读书笔记(2):CUDA内存的更多相关文章
- 《Java并发编程的艺术》读书笔记:二、Java并发机制的底层实现原理
二.Java并发机制底层实现原理 这里是我的<Java并发编程的艺术>读书笔记的第二篇,对前文有兴趣的朋友可以去这里看第一篇:一.并发编程的目的与挑战 有兴趣讨论的朋友可以给我留言! 1. ...
- 《jQuery基础教程》读书笔记
最近在看<jQuery基础教程>这本书,做了点读书笔记以备回顾,不定期更新. 第一章第二章比较基础,就此略过了... 第三章 事件 jQuery中$(document).ready()与j ...
- 《Java并发编程的艺术》读书笔记:一、并发编程的目的与挑战
发现自己有很多读书笔记了,但是一直都是自己闷头背,没有输出,突然想起还有博客圆这么个好平台给我留着位置,可不能荒废了. 此文读的书是<Jvava并发编程的艺术>,方腾飞等著,非常经典的一本 ...
- cuda编程基础
转自: http://blog.csdn.net/augusdi/article/details/12529247 CUDA编程模型 CUDA编程模型将CPU作为主机,GPU作为协处理器(co-pro ...
- 《深入java虚拟机》读书笔记之Java内存区域
前言 该读书笔记用于记录在学习<深入理解Java虚拟机--JVM高级特性与最佳实践>一书中的一些重要知识点,对其中的部分内容进行归纳,主要是方便之后进行复习. 运行时数据区域 Java虚拟 ...
- 《深入分析Java Web技术内幕》读书笔记之JVM内存管理
今天看JVM的过程中收获颇丰,但一想到这些学习心得将来可能被遗忘,便一阵恐慌,自觉得以后要开始坚持做读书笔记了. 操作系统层面的内存管理 物理内存是一切内存管理的基础,Java中使用的内存和应用程序的 ...
- Java并发编程实践读书笔记(2)多线程基础组件
同步容器 同步容器是指那些对所有的操作都进行加锁(synchronize)的容器.比如Vector.HashTable和Collections.synchronizedXXX返回系列对象: 可以看到, ...
- Java并发编程实践读书笔记(5) 线程池的使用
Executor与Task的耦合性 1,除非线程池很非常大,否则一个Task不要依赖同一个线程服务中的另外一个Task,因为这样容易造成死锁: 2,线程的执行是并行的,所以在设计Task的时候要考虑到 ...
- Java并发编程实践(读书笔记) 任务执行(未完)
任务的定义 大多数并发程序都是围绕任务进行管理的.任务就是抽象和离散的工作单元. 任务的执行策略 1.顺序的执行任务 这种策略的特点是一般只有按顺序处理到来的任务.一次只能处理一个任务,后来其它任 ...
- Java并发编程实践读书笔记(1)线程安全性和对象的共享
2.线程的安全性 2.1什么是线程安全 在多个线程访问的时候,程序还能"正确",那就是线程安全的. 无状态(可以理解为没有字段的类)的对象一定是线程安全的. 2.2 原子性 典型的 ...
随机推荐
- 消息推送平台的实时数仓?!flink消费kafka消息入到hive
大家好,3y啊.好些天没更新了,并没有偷懒,只不过一直在安装环境,差点都想放弃了. 上一次比较大的更新是做了austin的预览地址,把企业微信的应用和机器人消息各种的消息类型和功能给完善了.上一篇文章 ...
- #Powerbi函数学习 SELECTEDVALUE与ISFILTERED
Power BI中的DAX函数ISFILTERED可以用来判断一个表或者一个列是否被筛选器所影响. 这个函数的语法很简单,就是ISFILTERED(<table_or_column_name&g ...
- 2020-10-16:CAS知道么?底层实现? 会引发什么问题?如何解决ABA问题?
福哥答案2020-10-16:#福大大架构师每日一题# 简单回答:cmpxchg原子指令.aba,循环开销大,一个共享变量. [知乎](https://www.zhihu.com/question/4 ...
- 2021-05-04:给定一个非负整数c,你要判断是否存在两个整数a和b,使得a*a+b*b=c。【举例】c=5时,返回true。c=4时,返回true。c=3时,返回false。
2021-05-04:给定一个非负整数c,你要判断是否存在两个整数a和b,使得aa+bb=c.[举例]c=5时,返回true.c=4时,返回true.c=3时,返回false. 福大大 答案2021- ...
- Mysql- DDL/DML/DQL/DCL 数据库基本操作语句(持续更新中)
Mysql基本语法 前言: 在测试项目中经常需要使用到简单的Mysql语句,但是不知道语句结构是什么,经常在百度查来查去: 以下就是总结Mysql常用的基础操作语句: 只需要执行从创建开始执行示例中的 ...
- 【RocketMQ】NameServer总结
NameServer是一个注册中心,提供服务注册和服务发现的功能.NameServer可以集群部署,集群中每个节点都是对等的关系(没有像ZooKeeper那样在集群中选举出一个Master节点),节点 ...
- python 环境下使用PIP 报错的解决方法
最近做一个小程序项目,使用djangorestframework,安装restframework 出现错误,安装环境Python2.7:出现错误如下: "UnicodeEncodeErro ...
- R 语言关于 SSL 证书异常处理笔记
一.关于 TCGAbiolinks TCGAbiolinks 是一个用于 TCGA 数据综合分析的 R/BioConductor 软件包,能够通过 GDC Application Programmin ...
- BGP选路
实验拓扑 实验需求 现有三个自治系统,需要对R1访问R4的loopback-X数据走向进行精确控制: R1访问R4的loopback0走R2,通过在R1上修改本地优先级实现 R1访问R4的loopba ...
- 浅谈TCP和UDP
简介 在计算机网络中,TCP(传输控制协议)和UDP(用户数据报协议)是两个常用的传输层协议.它们分别提供了可靠的数据传输和快速的数据传送,成为互联网世界中的双子星.本文将探讨TCP和UDP的特点.优 ...