今天这个章节主要是说了GPU上不同种类的存储器, 在使用它们的时候, 一定情况下所需要的修饰符.
还记得前几天说过, 一张GPU能有不同的存储器种类吗? 今天这章节则是说, 如何在程序中使用它们.
传统上, 在GPU上运行的kernel里,直接定义或者访问的各种存储器上的变量, 数组之类的, 需要加上特定的前缀:
(1)Global memory: __device__前缀
(2)Constant Memory: __constant__前缀
(3)Shared memory: __shared__前缀
(4)Local memory: (不需要任何前缀, 编译器全自动处理)
注意这里的local memory比较奇特, 因为前几日说过, 它(local memory)是和寄存器一起使用的, 编译器能使用寄存器就使用寄存器, 不能则使用local memory(较慢),正因为是编译器自动处理的, 所以local memory不需要任何前缀.
那么本章节就剩下前3种存储器类型需要说明了.
根据以前的章节, 聪明的你已经知道, global memory是基于显存(或者映射的内存, 或者映射的其他的伙伴卡的显存---但这里为了简单统称显存)
显存是在GPU设备上的, 静态的定义可以直接使用:
__device__ your_type your_variable[...];
__global__ your_kernel()
{
//use your variable here
}
则是一种典型的静态定义.
另外一种则是手工的动态分配global memory, 例如通过cudaMalloc*()之类的函数.如果是动态定义的, 则需要单独将kernel做一个修改:
//删除 __device__ your_type your_variable[...];
__global__ your_kernel(your_type *your_variable)
{
//use your variable here
}
请注意这个动态分配的global memory和静态分配的global memory的区别:
(1)只有静态的才有__device__和具体定义
(2)动态的不出现直接的定义, 则是在Host Code中分配, 当成一个指针传递给kernel(注意*号)
静态定义的一般比较适合懒人. Kernel运行的时候, 需要的Global memory(显存)就已经准备好了.而动态的有更大的控制力, 适合需要更细微的代码控制能力的人使用.(此外, 静态的全局定义, 不符合很多现在编程模式所需要的, 只有输入和输出, 而没有全局状态的标准要求)
但是无论怎么说, 至少你已经知道了, 可以直接来个__device__定义显存上的变量或者数组了.
第二点则是: constant memory. 请注意在很早之前, 它的标准写法是:__device__ __constant__ your_type your_variable[...];
不过现在已经被大家缩写为__constant__这一个前缀了.不过本章节依然为你指出了, 同时使用2个前缀是可以的. 所以遇到老代码不要惊讶.
constant memory实际上在现在的卡种, 分成多个部分,
(1)你手工静态分配的constant memory, 最大64KB
(2)编译器自动搜集来的一些常量, 从你的代码中, 例如:
int c = a * 888 + 999;
编译器很可能将这里的888或者999或者全部都放入constant memory, 并全自动的使用它.
(注意这个只是可能. 编译器还有其他更好的位置放置它, 例如编译成立即数. 但是这里只提一下自动放入constant memory)
(3)你的kernel的参数, 在现在的卡中(2.0+)也将自动放入constant memory
请注意如果你一旦适合在程序里面进行对kernel的参数进行修改, 例如:
__global__ your_kernel(int *p.....)
{
//...
p ++;
}
类似这种代码, 则p将被自动生成一个同名的副本, 享受普通变量的待遇(自动放入寄存器或者local memory)
(4) 普通的global memory, 但用户要求通过constant cache进行读取, 例如用户知道一些非常小的常数数组, 而且warp内部的线程非常一致的访问同一个下标的时候(Load Uniform, LDU操作), 这个手册后面有说明.
大致这4种是constant memory的使用. 请注意, kernel参数这里是很多人经常疑惑的, 很多人担心, 访问kernel的参数代价非常高昂吗?
答案是否定的. 和你的普通__constant__一样代价很低.
很多用CUDA的人整天疑神疑鬼. 用这个会慢吗?用那个会慢吗?类似这种的——不会啊. 别乱想,真要感觉慢, 你先去考虑换一个好卡再说,很多时候不是你代码写的渣, 而是你的卡太烂。
然后请注意的另外一点是, 正常使用的, 你只有(1)中的手工__constant__静态分配的才能用到. 其他均不常用(要么不常用, 要么是编译器自动的, 你控制不了)
而__constant__的内容实质上是可以改变的, 只是在一个kernel运行的期间, 不能改变.在没有kernel运行的时候, 可以通过cudaMemcpyToSymbol之类的改变它里面的值. 给下个kernel用.所谓常数, 只是在一个kernel的运行期间常数罢了.
这点需要注意.(cudaMemcpyToSymbol等于普通的cudaGetSymbolAddress得到地址后 + 一个普通的cudaMemcpy而已. 直接cudaMemcpyToSymbol能简单一点)
这也是很多人经常在使用cudaMemcpyToSymbol时候的疑惑.特别是因为CUDA历史原因, Symbol的使用, 在不同时期的CUDA上, 有两种用法:一种是将你的变量名在Host中进行cudaMemcpyToSymbol的时候, 必须加引号:
例如:
有: __constant__ int dog_parameters[64];
那么在cudaMemcpyToSymbol的时候, 历史原因, 有两种用法:cudaMemcpyToSymbol("dog_parameters", .....);和cudaMemcpyToSymbol(dog_paramemters, .....);
这个问题是新手经常遇到的问题.特别是你看到了老书的时候(很多人手里头都是老书. 我们已经替无数本市面上的各家出版社的各本书进行debug了.....),现在的新版本CUDA只有没有引号的用法(下面那行)
维护老代码的人员, 或者手头还有老书的人员一定要注意这点. 这个是说的__constant__