首页 > 系统相关 >《CUDA编程:基础与实践》读书笔记(2):CUDA内存

《CUDA编程:基础与实践》读书笔记(2):CUDA内存

时间:2023-08-09 09:12:51浏览次数:40  
标签:__ 缓存 字节 读书笔记 线程 内存 全局 CUDA

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
From: https://www.cnblogs.com/moonzzz/p/17612766.html

相关文章

  • 《CUDA编程:基础与实践》读书笔记(1):CUDA编程基础
    1.GPU简介GPU与CPU的主要区别在于:CPU拥有少数几个快速的计算核心,而GPU拥有成百上千个不那么快速的计算核心。CPU中有更多的晶体管用于数据缓存和流程控制,而GPU中有更多的晶体管用于算数逻辑单元。所以,GPU依靠众多的计算核心来获得相对较高的并行计算性能。一块单独的GPU无......
  • C++ Primer Plus 第6版 读书笔记(8)第 8章 函数探幽
    第8章函数探幽本章内容包括:内联函数。引用变量。如何按引用传递函数参数。默认参数。函数重载。函数模板。函数模板具体化。通过第7章,您了解到很多有关C++函数的知识,但需要学习的知识还很多。C++还提供许多新的函数特性,使之有别于C语言。新特性包括内联函数、......
  • 04-非连续内存分配
    04-非连续内存分配为什么需要非连续内存分配连续内存分配的缺点1)分配给一个程序的物理内存是连续的2)内存利用率较低3)有外碎片、内碎片的问题非连续内存分配的优点一个程序的物理地址空间是非连续的更好的内存利用和管理允许共享代码与数据(共享库)支持动态加载和动态链接......
  • 【JVM技术指南】「GC内存诊断-故障问题排查」一文教你如何打印及分析JVM的GC日志(实战
    当我们在开发Java应用程序时,JVM的GC(垃圾回收)是一个非常重要的话题。GC的作用是回收不再使用的内存,以便程序可以继续运行。在JVM中,GC的日志记录了GC的详细信息,包括GC的类型、时间、内存使用情况等。在本文中,我们将介绍JVMGC日志的格式、含义和分析方法。JVMGC日志格式JVMGC日志的......
  • JVM学习之:堆(Heap)和非堆(Non-heap)内存
    JVM学习之:堆(Heap)和非堆(Non-heap)内存 堆(Heap)和非堆(Non-heap)内存:堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在Java虚拟机启动时创建的。“在JVM中堆之外的内存称为非堆内存(Non-heapmemory)”。可以看出JVM主要管理两种类型的内存:堆和非堆。简单来......
  • 苹果正在测试新款Mac mini:搭载M3芯片 配备24GB大内存
    据悉苹果目前正在测试新的Mac机型,亮点是采用最新的M3芯片。据报道,首款搭载M3芯片的设备应该是13英寸的MacBookPro和重新设计的MacBookAir,Macmini机型并不在名单上。M3和M2同样拥有最多8个核心,分别为4个性能核和4个能效核,以及最多10核心的GPU,支持最大24GB的统一内存。基本款......
  • 深入理解Linux内核——内存管理(1)
    提要:系列文章主要参考MIT6.828课程以及两本书籍《深入理解Linux内核》``《深入Linux内核架构》对Linux内核内容进行总结。内存管理的实现覆盖了多个领域:内存中的物理内存页的管理分配戴爱内存的伙伴系统分配较小内存的slab、slub、slob分配器分配非连续内存块的vmalloc分配......
  • 国标GB28181视频平台LntonGBS(源码版)国标视频平台内存错误导致崩溃的问题解决方案
    LntonGBS国标视频云服务通过支持国标GB28181协议,实现了设备接入、实时监控直播、录像、语音对讲、云存储、告警、级联等功能。同时,它还支持将接入的视频流以多种格式(包括RTSP、RTMP、FLV、HLS、WebRTC)进行全终端、全平台分发,实现了无插件播放在Web浏览器、手机浏览器、微信端、PC客......
  • Java内存解析(转)
    栈、堆、常量池等虽同属Java内存分配时操作的区域,但其适用范围和功用却大不相同。本文将深入Java核心,简单讲解Java内存分配方面的知识。首先我们先来讲解一下内存中的各个区域。 stack(栈):存放基本类型的数据和对象的引用,即存放变量。如果存放的事基本类型的数据(非静态变量),则直接......
  • 内存溢出后重启IIS后,附加到进程调试 ,找不到w3wp.exe进程。
    打开IIS,右键浏览一下。这个时候附加到进程调试里的选项就有了!本文来自博客园,作者:小二↑上酒,转载请注明原文链接:https://www.cnblogs.com/qh1688/p/7383925.html......