1. 全局内存

核函数中的所有线程都能够访问全局内存(global memory)。全局内存的容量是所有设备内存中最大的,但由于它没有放在GPU芯片内部,因此具有相对较高的延迟和较低的访问速度,cudaMalloc分配的就是全局内存。此外,当处理逻辑上的二维或者三维问题时,还可以使用cudaMallocPitchcudaMalloc3D分配内存,用cudaMemcpy2DcudaMemcpy3D复制数据,释放时依然使用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)变量仅对一个线程可见,它是所有内存中访问速度最高的。在核函数中定义的不加任何限定符的变量一般来说就存放在寄存器中。在核函数中定义的不加任何限定符的数组有可能存放在寄存器中、也有可能存放在局部内存中。各种内建变量,例如gridDimblockDimblockIdxthreadIdx以及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内存的更多相关文章

  1. 《Java并发编程的艺术》读书笔记:二、Java并发机制的底层实现原理

    二.Java并发机制底层实现原理 这里是我的<Java并发编程的艺术>读书笔记的第二篇,对前文有兴趣的朋友可以去这里看第一篇:一.并发编程的目的与挑战 有兴趣讨论的朋友可以给我留言! 1. ...

  2. 《jQuery基础教程》读书笔记

    最近在看<jQuery基础教程>这本书,做了点读书笔记以备回顾,不定期更新. 第一章第二章比较基础,就此略过了... 第三章 事件 jQuery中$(document).ready()与j ...

  3. 《Java并发编程的艺术》读书笔记:一、并发编程的目的与挑战

    发现自己有很多读书笔记了,但是一直都是自己闷头背,没有输出,突然想起还有博客圆这么个好平台给我留着位置,可不能荒废了. 此文读的书是<Jvava并发编程的艺术>,方腾飞等著,非常经典的一本 ...

  4. cuda编程基础

    转自: http://blog.csdn.net/augusdi/article/details/12529247 CUDA编程模型 CUDA编程模型将CPU作为主机,GPU作为协处理器(co-pro ...

  5. 《深入java虚拟机》读书笔记之Java内存区域

    前言 该读书笔记用于记录在学习<深入理解Java虚拟机--JVM高级特性与最佳实践>一书中的一些重要知识点,对其中的部分内容进行归纳,主要是方便之后进行复习. 运行时数据区域 Java虚拟 ...

  6. 《深入分析Java Web技术内幕》读书笔记之JVM内存管理

    今天看JVM的过程中收获颇丰,但一想到这些学习心得将来可能被遗忘,便一阵恐慌,自觉得以后要开始坚持做读书笔记了. 操作系统层面的内存管理 物理内存是一切内存管理的基础,Java中使用的内存和应用程序的 ...

  7. Java并发编程实践读书笔记(2)多线程基础组件

    同步容器 同步容器是指那些对所有的操作都进行加锁(synchronize)的容器.比如Vector.HashTable和Collections.synchronizedXXX返回系列对象: 可以看到, ...

  8. Java并发编程实践读书笔记(5) 线程池的使用

    Executor与Task的耦合性 1,除非线程池很非常大,否则一个Task不要依赖同一个线程服务中的另外一个Task,因为这样容易造成死锁: 2,线程的执行是并行的,所以在设计Task的时候要考虑到 ...

  9. Java并发编程实践(读书笔记) 任务执行(未完)

    任务的定义 大多数并发程序都是围绕任务进行管理的.任务就是抽象和离散的工作单元.   任务的执行策略 1.顺序的执行任务 这种策略的特点是一般只有按顺序处理到来的任务.一次只能处理一个任务,后来其它任 ...

  10. Java并发编程实践读书笔记(1)线程安全性和对象的共享

    2.线程的安全性 2.1什么是线程安全 在多个线程访问的时候,程序还能"正确",那就是线程安全的. 无状态(可以理解为没有字段的类)的对象一定是线程安全的. 2.2 原子性 典型的 ...

随机推荐

  1. allure测试报告美化与定制

    一份简单的测试报告 一份简单的测试报告可以使用pytest的插件html就可以生成, demo如下 先下载 pip install pytest-html 下载完之后,在当前执行过测试用例的测试目录下 ...

  2. 2023-04-09:使用 Golang 重写的 ffmpeg 示例encode_video.c,实现视频编码并将编码后的数据封装为容器格式,最终写入输出文件。

    2023-04-09:使用 Golang 重写的 ffmpeg 示例encode_video.c,实现视频编码并将编码后的数据封装为容器格式,最终写入输出文件. 答案2023-04-09: 本文介绍的 ...

  3. 2022-09-30:以下go语言代码输出什么?A: true true false true false; B: true false false true false; C: true true

    2022-09-30:以下go语言代码输出什么?A: true true false true false: B: true false false true false: C: true true ...

  4. 2020-10-18:java中LongAdder和AtomicLong有什么区别?

    福哥答案2020-10-18:#福大大架构师每日一题# 简单回答:AtomicLong是CAS操作.LongAdder是多个单元操作. 中级回答:AtomicLong 是基于 CAS 方式自旋更新的: ...

  5. Hugging News #0519: 我们把 HuggingChat 开源了

    每一周,我们的同事都会向社区的成员们发布一些关于 Hugging Face 相关的更新,包括我们的产品和平台更新.社区活动.学习资源和内容更新.开源库和模型更新等,我们将其称之为「Hugging Ne ...

  6. 代码随想录算法训练营Day45 动态规划

    代码随想录算法训练营 代码随想录算法训练营Day45 动态规划|70. 爬楼梯(进阶) 322. 零钱兑换 70. 爬楼梯 (进阶) 题目链接:70. 爬楼梯 (进阶 假设你正在爬楼梯.需要 n 阶你 ...

  7. 代码随想录算法训练营Day15 二叉树| 层序遍历 10 226.翻转二叉树 101.对称二叉树 2

    代码随想录算法训练营 代码随想录算法训练营Day15 二叉树| 层序遍历 10 226.翻转二叉树 101.对称二叉树 2 层序遍历10 题目链接:层序遍历10 给你二叉树的根节点 root ,返回其 ...

  8. yolotv5和resnet152模型预测

    我已经训练完成了yolov5检测和resnet152分类的模型,下面开始对一张图片进行检测分类. 首先用yolo算法对猫和狗进行检测,然后将检测到的目标进行裁剪,然后用resnet152对裁剪的图片进 ...

  9. kprobe_events shell模式使用教程

    kprobe_events shell模式使用教程 kprobe 使用前提 需要内核启用以下配置 CONFIG_KPROBES=y CONFIG_HAVE_KPROBES=y CONFIG_KPROB ...

  10. 2023-06-06:给你二叉树的根结点 root ,请你设计算法计算二叉树的 垂序遍历 序列。 对位于 (row, col) 的每个结点而言, 其左右子结点分别位于 (row + 1, col -

    2023-06-06:给你二叉树的根结点 root ,请你设计算法计算二叉树的 垂序遍历 序列. 对位于 (row, col) 的每个结点而言, 其左右子结点分别位于 (row + 1, col - ...