GPU编程 内存类型简介

内存分类

存储空间主要分为可编程不可编程两种。

通常在存储结构中,缓存都是不可编程的。(例如:L1和L2 cache)

GPU的可编程存储空间有:Global, local, texture, constant, shared and register memory。

按照速度和大小对他们进行排序如下图所示:

在这里插入图片描述

内存功能

各部分存储空间的分布情况如下图所示:
在这里插入图片描述
从上图不难看出,GPU每个线程块内实际只有寄存器、共享内存、本地内存三种存储器。其余常量、纹理、全局内存都存放在线程外。

虽然内存的速度似乎是最越快越好,但决定如何选择内存的类型的其他两个特征是内存的范围和生存期。

Registers

存储在寄存器内存中的数据仅对创建该存储器的线程可见,并在该线程的运行期间持续。kernel中没有什么特殊声明的自动变量都是放在寄存器中的。当数组的索引是constant类型且在编译期能被确定的话,就是内置类型,数组也是放在寄存器中。

寄存器的资源较少,相对于其它类型的内存资源稀少。通过regsPerBlock属性能看出每个线程块中最大可包含的寄存器数量。**在Fermi上,每个thread限制最多拥有63个register,Kepler则是255个。**在设计kernel时使用较少的register就能允许更多的block驻留在SM中,也就增加了占有率,提高了性能。

大多数情况下,访问寄存器每个指令消耗零时钟周期。但是,由于写入和读取,可能会发生延迟。延迟大约为 24 个时钟周期。对于每个SM具有 32 个线程的较新的 CUDA 设备,可能需要多达 768 个线程才能完全隐藏延迟。

Local Memory

本地内存不是内存的物理类型,而是全局内存的抽象。它的范围是线程的本地,但驻留在芯片外,这使得它作为全局内存访问成本高昂。本地内存仅用于保存自动变量。当编译器确定没有足够的寄存器空间来保存变量时,编译器会使用本地内存。大型结构或数组的自动变量通常也放置在本地内存中。

有以下几种情况会使用本地内存:
1.当所有的寄存器内存被占用后就会开始使用本地内存(被称为register spilling)。
2.编译期间无法确定确切值的本地数组。
3.较大的结构体和数组

Shared Memory

放在共享内存的变量需要通过_shared_来修饰。共享内存存放在片上,因此相较于Local Memory和Global Memory具有较高的带宽和很低的延时。

不同于Register,Shared memory尽管在kernel里声明的,但是他的生命周期是伴随整个block,而不是单个thread。当该block执行完毕,他所拥有的资源就会被释放,重新分配给别的block。

存储在共享内存中的数据对该块内的所有线程可见,并持续到块的持续时间。这是非常宝贵的,因为这种类型的内存允许线程相互通信和共享数据。
存储在全局内存中的数据对应用程序内的所有线程(包括主机)都可见,并且持续于主机分配的持续时间。

内存可以共同使用的同时,同时带来了访问冲突的问题。为了解决这一瓶颈,在常用的GPU架构中线程按照Warpsize属性划分为32个一组,连续的共享内存分配给连续的线程组。

Constant Memory

Constant Memory和Texture Memory只对非常特定的应用程序类型有益。Constant Memory用于在内核执行过程中不会更改且仅读的数据。使用Constant Memory而不是Global Memory可以减少所需的内存带宽,但只有当线程读取同一位置时,才能实现此性能增益。Constant Memory的初始化如下面公式所示:

cudaError_t cudaMemcpyToSymbol(const void* symbol, const void* src,size_t count);

当一个warp中所有thread都从同一个Memory地址读取数据时,Constant Memory表现最好。例如,计算公式中的系数。如果所有的thread从不同的地址读取数据,并且只读一次,那么Constant Memory就不是很好的选择,因为一次读Constant Memory操作会广播给所有thread知道。

Texture Memory

与常量内存相似,纹理内存是设备上的另一种只读内存。当线程中的所有读取在物理上相邻时,与全局内存相比,使用纹理内存可以减少内存流量并提高性能。

Global Memory

Global Memory时空间最大,延时最高,GPU最基础的内存单元。任意SM都可以在整个程序的生命期中获取全局内存的数据。global中的变量既可以是静态也可以是动态声明。可以使用__device__修饰符来限定其属性。global memory的分配就是之前频繁使用的cudaMalloc,释放使用cudaFree。global memory驻留在Device Memory,可以通过32-byte、64-byte或者128-byte三种格式传输。

一般来说,所需要的transaction越多,潜在的不必要数据传输就越多,从而导致传输速率的降低。

设计思想

大多数CUDA程序都是逐渐成熟的,一开始使用全局内存初始化,初始化完毕后考虑使用其它类型的内存,例如零复制内存(zero-copy memory)、共享内存、常量内存,最终寄存器也要被考虑进来。为了优化一个程序,需要在程序的设计之初就要考虑使用较快速度的存储器,并且精确知道在何处以及如何提高程序性能。此外不仅要思考如何更高效的访问全局内存,也要时刻想办法减少对全局内存的访问次数,尤其在数据被重复利用的时候。

猜你喜欢

转载自blog.csdn.net/daijingxin/article/details/109352757
今日推荐