肥宅钓鱼网
当前位置: 首页 钓鱼百科

cuda使用共享内存(CUDA加速共享内存介绍及其应用)

时间:2023-07-03 作者: 小编 阅读量: 1 栏目名: 钓鱼百科

不同类型的内存有各自不同的特点,不过片上内存通常比板载显存要快,而寄存器又是所有存储种类中最快的。本文我们着重介绍共享内存的基础知识以及应用例子。此方式特点为定义的时候不指定大小,在调用核函数的时候将共享内存大小以输入参数的形式传入。

CUDA的存储器可以大致分为两类:

  • 板载显存(On-board memory)
  • 片上内存(On-chip memory)

其中板载显存主要包括全局内存(global memory)、本地内存(local memory)、常量内存(constant memory)、纹理内存(texture memory)等,片上内存主要包括寄存器(register)和共享内存(shared memory)。不同类型的内存有各自不同的特点,不过片上内存通常比板载显存要快,而寄存器又是所有存储种类中最快的。本文我们着重介绍共享内存的基础知识以及应用例子。


01

查看自己显卡上的共享内存信息

CUDA提供了cudaGetDeviceCount和cudaGetDeviceProperties这两个函数,分别用于获取CUDA设备数、获取CUDA设备属性,通过调用这两个函数,可以方便获取共享内存信息和其它CUDA设备信息:

//显示CUDA设备信息void show_GPU_info(void){int deviceCount;//获取CUDA设备总数cudaGetDeviceCount(&deviceCount);//分别获取每个CUDA设备的信息for(int i=0;i<deviceCount;i){//定义存储信息的结构体cudaDeviceProp devProp;//将第i个CUDA设备的信息写入结构体中cudaGetDeviceProperties(&devProp, i);std::cout << "使用GPU device " << i << ": " << devProp.name << std::endl;std::cout << "设备全局内存总量:" << devProp.totalGlobalMem / 1024 / 1024 << "MB" << std::endl;std::cout << "SM的数量:" << devProp.multiProcessorCount << std::endl;std::cout << "每个线程块的共享内存大小:" << devProp.sharedMemPerBlock / 1024.0 << " KB" << std::endl;std::cout << "每个线程块的最大线程数:" << devProp.maxThreadsPerBlock << std::endl;std::cout << "设备上一个线程块(Block)中可用的32位寄存器数量: " << devProp.regsPerBlock << std::endl;std::cout << "每个EM的最大线程数:" << devProp.maxThreadsPerMultiProcessor << std::endl;std::cout << "每个EM的最大线程束数:" << devProp.maxThreadsPerMultiProcessor / 32 << std::endl;std::cout << "设备上多处理器的数量:" << devProp.multiProcessorCount << std::endl;std::cout << "======================================================" << std::endl;}}

运行以上函数,得到共享内存信息以及其它设备信息,如下图所示,本人使用的显卡上,针对于每一个线程块,其可以使用的最大共享内存为48 KB。


02

共享内存的特性

共享内存的主要特点在于“共享”,也即同一个线程块中的所有线程都可以对这一块存储进行读写操作,所以“共享”是针对同一个线程块中所有线程而言的。一旦共享内存被定义并指定大小,系统将给所有线程块都分配相同大小的共享内存,比如定义一个大小为8 bytes的unsigned char型共享内存,那么所有线程块都会被分配一个8 bytes的unsigned char型共享内存。如下图所示:

共享内存在CUDA核函数中定义,通常有两种方式:静态方式、动态方式。

  • 静态方式定义。这种方式定义的特点是定义的同时指定大小

__global__ shared_memory_kernel(uchar *inputs, int row, int col){int x = threadIdx.xblockDim.x * blockIdx.x;//colint y = threadIdx.yblockDim.y * blockIdx.y;//rowif (x < col && y < row){__shared__ uchar s[8];//定义的同时指定大小为8 bytes,因此每个线程块都被分配8 bytes的共享内存...}}

  • 动态方式定义。此方式特点为定义的时候不指定大小,在调用核函数的时候将共享内存大小以输入参数的形式传入

__global__ shared_memory_kernel(uchar *inputs, int row, int col){int x = threadIdx.xblockDim.x * blockIdx.x;//colint y = threadIdx.yblockDim.y * blockIdx.y;//rowif (x < col && y < row){extern __shared__ uchar s[];//定义的时候不指定大小...}}void shared_memory_test(void){...//传入的第1个参数block_num为线程块总数//第2个参数thread_num为每个线程块包含的线程数//第3个参数8为共享内存大小,所以动态共享内存大小通过第3个参数传入shared_memory_kernel<<<block_num, thread_num, 8>>>(inputs, row, col);...}

需要注意:动态定义共享内存时,调用核函数传入的数值必须以字节byte为单位,所以如果定义的共享内存不是byte类型,数值必须乘以类型占用的字节数。比如要动态定义长度为8的float类型共享内存,那么传入核函数的数值为8*sizeof(float)。

shared_memory_kernel<<<block_num, thread_num, 8 * sizeof(float)>>>(inputs, row, col);


03

共享内存的应用例子

前文我们讲的数组元素规约求和算法,使用CUDA全局内存来存储数据:

CUDA加速——基于规约思想的数组元素求和

我们知道全局内存属于板载显存,而共享内存属于片上内存,因此共享内存的读写速度比全局内存快得多。在前文代码的核函数中有个for循环需要多次读写全局内存,全局内存本身就很慢,而且如果不是连续访问会更慢,因此本文我们尝试使用共享内存来代替全局内存实现前文讲的规约求和算法。

由于前文的规约算法是在不同线程块分别进行的,而共享内存又具有线程块内共享的特性,故共享内存正好适合此应用场景。

前文的规约结构

本文使用共享内存的规约结构

下面我们比较使用共享内存的核函数与前文使用全局内存的核函数:

//使用全局内存__global__ void cal_sum_ker0(float *Para, float *blocksum_cuda){int tid = blockIdx.x * blockDim.xthreadIdx.x;if(tid < N){for (int index = 1; index < blockDim.x; index = (index*2)){if (threadIdx.x % (index*2) == 0){Para[tid]= Para[tidindex];}__syncthreads();//同步,以防止归约过程中某个线程运行速度过快导致计算错误}if(threadIdx.x == 0)//整个数组相加完成后,将共享内存数组0号元素的值赋给全局内存数组0号元素blocksum_cuda[blockIdx.x] = Para[tid];}}//使用共享内存//blockIdx.x为线程块的ID号//blockDim.x每个线程块中包含的线程总个数//threadIdx.x为每个线程块中的线程ID号__global__ void cal_sum_ker(float *Para, float *blocksum_cuda){int tid = blockIdx.x * blockDim.xthreadIdx.x;if(tid < N){//动态方式定义float型共享内存extern __shared__ float s_Para[];//线程块中的每个线程负责把其对应的数据从全局内存加载到共享内存s_Para[threadIdx.x] = Para[tid];__syncthreads();//块内线程同步,等待线程块内所有线程加载数据完毕for (int index = 1; index < blockDim.x; index = (index*2)){if (threadIdx.x % (index*2) == 0){//在for循环中使用共享内存实现规约,避免频繁读写全局内存s_Para[threadIdx.x]= s_Para[threadIdx.xindex];}__syncthreads();//块内线程同步,以防止归约过程中某个线程运行速度过快导致计算错误}if(threadIdx.x == 0)//将共享内存数组0号元素的值赋给全局内存数组blocksum_cuda[blockIdx.x] = s_Para[threadIdx.x];}}

接着在测试代码中分别调用上方两个核函数。调用时指定共享内存的长度为每个线程块包含的线程数:

void cal_sum_test() {Timer_Us2 timer;//定义CPU端数组float *test_d = (float *)malloc(N * sizeof(float));for (long long i = 0; i < N; i){test_d[i] = 0.5;}dim3 sumblock(512);//设置每个线程块有512个线程dim3 sumgrid(((N%sumblock.x) ? (N/sumblock.x1) : (N/sumblock.x)));float *test_d_cuda;float *blocksum_cuda;float *blocksum_host = (float *)malloc(sizeof(float) * sumgrid.x);cudaMalloc((void **)&test_d_cuda, sizeof(float) * N);cudaMalloc((void **)&blocksum_cuda, sizeof(float) * sumgrid.x);timer.start_timer();//将数据从CPU端拷贝到GPU端cudaMemcpy(test_d_cuda, test_d, sizeof(float) * N, cudaMemcpyHostToDevice);//调用使用全局内存规约的核函数cal_sum_ker0 << < sumgrid, sumblock>> > (test_d_cuda, blocksum_cuda);//将所有线程块的规约结果从GPU端拷贝到CPU端cudaMemcpy(blocksum_host, blocksum_cuda, sizeof(float) * sumgrid.x, cudaMemcpyDeviceToHost);//在CPU端对所有线程块的规约求和结果做串行求和double sum = 0.0;for(int i = 0; i < sumgrid.x; i){sum= blocksum_host[i];}timer.stop_timer("GPU time (global memory):");cout << " GPU result (global memory) = " << sum << endl;//显示GPU端结果//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////timer.start_timer();cudaMemcpy(test_d_cuda, test_d, sizeof(float) * N, cudaMemcpyHostToDevice);//调用使用共享内存规约的核函数,sumblock.x为每个线程块包含的线程数,sumblock.x * sizeof(float)就是传入的共享内存字节数cal_sum_ker << < sumgrid, sumblock, sumblock.x * sizeof(float) >> > (test_d_cuda, blocksum_cuda);cudaMemcpy(blocksum_host, blocksum_cuda, sizeof(float) * sumgrid.x, cudaMemcpyDeviceToHost);sum = 0.0;for(int i = 0; i < sumgrid.x; i){sum= blocksum_host[i];}timer.stop_timer("GPU time (shared memory):");cout << " GPU result (shared memory) = " << sum << endl;//显示GPU端结果cudaFree(test_d_cuda);cudaFree(blocksum_cuda);free(blocksum_host);free(test_d);}

运行结果如下,可以看到使用共享内存之后,耗时减少了,这是因为共享内存的读写效率比全局内存高。

    推荐阅读
  • 中长款雪纺衫外穿搭配(今年夏天火了这种减龄穿搭)

    黑色T恤显瘦遮肉,戴上银色项链更显时尚感,潮流中带有贵气。脚上一双淡蓝色凉鞋,清爽舒适,充满了夏天的气息。脚上一双淡蓝色凉鞋,淡雅清新,手上拎着一款白色小包包,优雅时尚,更添了几分精致感。下身搭配黑色短裤,休闲又舒适,还能够露出纤细的大长腿,显出高挑曼妙的身材。略显拖地的设计能够拉伸身材比例,显得人更加高挑。手上拎着一款黑色牛津包,看上去很大气,为整体增添了时尚元素,显得更有气质。

  • 华为圣诞树简笔画手机备忘录(华为圣诞树用什么软件画的)

    圣诞树简笔画圣诞树简笔画procreate我的随手画人文绘画,下面我们就来选几个华为圣诞树简笔画手机备忘录?我们一起去看看这些简笔画吧!华为圣诞树简笔画手机备忘录圣诞树简笔画圣诞树简笔画procreate我的随手画人文绘画圣诞树怎么画圣诞树简笔画画法圣诞树简笔画iphone备忘录手绘圣诞树圣诞树简笔画圣诞节画画图片简笔画小学生圣诞树简笔画学习圣诞树简笔画iphone备忘录手绘圣诞树五张简单的圣诞树简笔画圣诞树简笔画圣诞树绘画素材圣诞树素材简笔画

  • 描写小舟优美的句子(赞美小舟的句子介绍)

    描写小舟优美的句子远处渔船上的白帆也被这初升的太阳染红了,像一面面巨大的红绸在飘扬。到了急流处,橡皮艇上下起伏,好像云霄飞车似的。然后,水鸟会飞到我的身上,梳理自己光洁的羽毛。渔船都扬起白帆,往来拉网,仿佛是成群结队翩翩飞舞的白蝴蝶。船如一枚没羽箭,在平静无波的长潭中来去如飞。孤帆远影碧空尽,唯见长江天际流口。小船如一片树叶,卷进滔滔的激流之中。平底船掠过水上,没有声息。

  • 咏鹅描写了鹅的什么(咏鹅描写了鹅的什么部位)

    《咏鹅》描绘了鹅在水中嬉戏的情境。全诗先通过鹅的独特叫声引读者入画,而后勾勒出鹅“曲项”的外形和“向天歌”的得意神态,以细腻又别致的笔调,鲜明又简洁地勾勒出鹅的可爱情态。《咏鹅》的赏析《咏鹅》是唐代诗人骆宾王在七岁时写就的短诗,表现出小诗人敏锐的观察能力。全诗第一句就连用三个“鹅”字,表达了诗人对鹅十分喜爱之情。次句“曲项向天歌”,描写鹅鸣叫的神态。“曲项”二字形容鹅向天高歌之态,十分确切。

  • 鲞怎么读(鲞什么意思)

    以下内容大家不妨参考一二希望能帮到您!鲞怎么读鲞,是一个汉字,拼音是xiǎng,本义为剖开晾干的鱼,后泛指成片的腌腊食品。康熙字典:鲞·康熙笔画:17·部外笔画:6;《正字通》俗鲞字。

  • 微信计步怎么开(如何开微信计步)

    跟着小编一起来看一看吧!微信计步怎么开首先打开微信,接着点击右上角的加号按钮,选择添加朋友。然后选择公众号,在输入框输入“微信运动”,点击我们搜索到的微信运动。选择启用该功能即可。如果你只是想由微信运动记录自己每天的步数,并不想它总是提示自己一些其它乱七八糟的消息。在微信运动的隐私设置中也可点选是否加入排行榜、是否接受排行榜消息、是否接收朋友点赞消息。

  • 李雪琴吴亦凡什么梗(李雪琴吴亦凡怎么回事)

    李雪琴吴亦凡什么梗李雪琴是吴亦凡的一个粉丝,她在抖音上录制了一条视频,视频中她用东北口音向吴亦凡问好,紧接着吴亦凡也做出了回应,随后大家都开始跟着争相模仿。李雪琴视频原文:“吴亦凡你好,我是李雪琴,我现在来到了清华大学,你看这校门多白~”“吴亦凡你好,我是李雪琴,我现在来到了圆明园,吴亦凡你吃饭了吗?”很多网友都调侃“吴亦凡被玩坏了”,以这样的方式和粉丝互动也是很幽默。

  • 商品橱窗开通后怎么选类目(商品橱窗开通后怎么选类目的方法)

    下面内容希望能帮助到你,我们来一起看看吧!商品橱窗开通后怎么选类目硬性条件:10000粉丝以上。

  • 如何成为能察言观色的人(怎样做到可以察言观色)

    察言观色是一切人情往来中操纵自如的基本技术。不会察言观色,等于不知风向便去转动舵柄,世事国通无从谈起,弄不好还会在小风浪中翻了船。直觉虽然敏感却容易受人蒙蔽,懂得如何推理和判断才是察言观色所追求的顶级技艺。言谈能告诉你一个人的地位、性格、品质及至流露内心情绪,因此善听弦外之音是“察言”的关键所在。我们如能真的在交际中察言观色,随机应变,也是一种本领。

  • 身上带什么可以防静电(可以防静电的随身物品)

    可带些小金属器件、棉抹布等先碰触车门、门把等消除静电,再用手触及。可以随手携带轻巧的加湿器。喷在有静电的地方可以防止静电,增加湿度,使局部的静电容易释放。对付静电,可以应该尽量选用纯棉制品作为衣物和家居饰物的面料,尽量避免使用化纤地毯和以塑料为表面材料的用品,以防止摩擦起电。冬季常洗手、多洗澡、勤换衣服这样对消除皮肤上的静电很有好处,或者先把手放墙上抹一下去除静电。