CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第五节
第五节:了解和使用共享内存(2)
Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Laboratory)的高级科研人员。他在多个国家级的实验室进行大型并行运算的研究,并且是几个新创企业的合伙人。大家可以发邮件到rmfarber@gmail.com与他沟通和交流。
在CUDA系列文章的第四节里,我探讨了执行模型和内核启动执行配置是如何影响寄存器的数量和本地多处理器资源如共享内存的数量的。在本小节,我会继续探讨内存性能,和在reverseArray_multiblock_fast.cu.内使用共享内存。
CUDA 内存性能
本地内存空间和全局内存空间不会缓存,这意味着每次对全局内存(或本地内存)进行访问都将导致一次实际的内存访问。那么访问(例如读取或写入)各种类型的内存的“开销”是多少?
多处理器需要四(4)个时钟周期才能为每次“warp(交换)”发出一条内存指令。读取本地或全局内存。访问本地或全局内存将导致400到600个时钟周期的延迟。举例说明,在以下代码片段中的赋值运算符需要4个时钟周期才能从全局内存中进行一次读取,4个时钟周期从共享内存进行一次写入,需要400到600个时钟周期从全局内存读取一个浮点值。注意:4个时钟周期从共享内存进行一次写入,需要400到600个时钟周期从全局内存读取一个浮点值。注意:使用__device__变量类型限定符表示全局内存中的变量(有关其他变量特征,请参见CUDA Programming Guide第4.2.2.1节)。主代码不能访问变量类型__device__。
__shared__ float shared[]; __device__ float device[]; shared[threadIdx.x] = device[threadIdx.x];
当访问时间出现100-150倍差别时,难怪程序开发人员会需要最小化对全局内存的访问,并且在本地多处理器内存内重新使用数据。CUDA设计者对线程调度程序的设计十分巧妙,大量的全局内存延迟都可以透明地隐藏起来:只需在执行配置中指定大量数据块,并尽可能在内核中使用寄存器、__shared__和__constant__存储器类型处理变量即可。
因为共享内存在芯片上,因此访问速度要比读取全局内存快很多,并且主要的优化是为了避免存储器组冲突。共享内存速度较快(有些文章认为它和寄存器访问一样快捷)。
然而,最近CUBLAS和CUFFT性能获得极大的改进:通过尽量使用共享内存而非寄存器-因此如可能的话,尽量使用寄存器吧。CUDA共享内存被分为大小相同的内存模块,称为存储器组(memorybank)。每个存储器组都保存有一个连续的32位值(如int和float),因此通过连续线程对连续数组访问非常的快捷。当向同一个存储数组(可能是同一个地址或映射到同一个存储数组的多个地址)发出数据请求时,存储器组发生冲突。如果发生这种情况,硬件会有效地序列化内存运算,强迫所有线程等待直到内存请求得以完成。如果所有的线程从同一个共享内存地址读取,那就会自动调用广播机制,避免序列化。共享内存广播是一个同时向多个线程提供数据的有效方法。使用共享内存时,您完全可以尝试下利用这一特点。
我将在下一个专栏文章中更为详细地讨论存储器组冲突。现在,只需要知道reverseArray_multiblock_fast.cu 没有存储器冲突,因为连续线程方位连续值。
具有读取/写入功能的多处理器本地存储器类型总结如下:
寄存器
- 多处理器上最快的内存形式;
- 仅可通过线程访问;
- 有线程的生命周期
共享内存
- 在没有存储器组冲突(从同一个地址读取)时与寄存器一样快。
- 可从创建线程的任何块访问;
- 有线程的生命周期
全局内存:
- 可能比寄存器或共享内存慢150倍,注意非联合读取和写入(将在下一专栏中讨论)。
- 可从主机或设备访问;
- 有应用程序的生命周期
本地内存:
- 潜在的性能缺陷,位于全局内存中,可能比寄存器或共享内存慢150倍。
- 仅可通过线程访问;
- 有线程的生命周期
共享内存注意事项
小心共享内存存储器冲突,可能会导致性能降低;
所有在内核里的动态分配的共享变量在同一个内存地址开始。使用至少两个动态分配共享内存数组要求手动生成偏移量。例如,如果你想动态分配共享内存,以包含两个数组, a 和b, 你需要进行如下操作:
__global__ void kernel(int aSize)
{
extern __shared__ float sData[];
float *a, *b;
a = sData;
b = &a[aSize]; __global__ void kernel(int aSize)
{
extern __shared__ float sData[];
float *a, *b;
a = sData;
b = &a[aSize];
寄存器/本地内存注意事项
寄存器内存可以透明地存入本地内存。这可能会造成性能欠佳。检查ptx 汇编代码或在通过nvcc在输出中查找lmem,命令如下:“—ptxas-options=-v”。
编译时已经知道的通过常量索引的数组通常存于寄存器中,但是如果使用变量索引,它们就不能存于寄存器中。这就为开发人员弄出了一个难题,因为可能需要循环展开以在寄存器内存,而非较慢的全局内存中,保存数组元素。然而,展开循环会急剧增加寄存器使用量,因而就可能会使得变量被保存在本地内存中――从而抵消了循环展开的诸多好处。可以使用使用nvcc选项,“—maxrregcount=value”告诉编译器使用更多寄存器(注意:可以指定的最大寄存器数量为128)。这需要在“使用更多的寄存器”和“创建更少的线程”之间权衡利弊,有可能会妨碍隐藏存储器延迟。在某些架构中,使用该选项可能造成资源不足,从而导致内核无法启动。
共享内存内核
程序reverseArray_multiblock.cu和revereseArray_multiblock_fast.cu 执行同样的任务。它们创建一个一维整数数组h_a,数组包含整数值[0 ..dimA-1]。数组可以通过cudaMemcpy移动到设备,然后主机启动reverseArrayBlock内核就地逆转数组内容的顺序。再次使用cudaMemcpy将数据从设备传回主机,并执行一个检查,检查设备生成了正确的结果(例如[dimA-1 .. 0])。
区别就是:reverseArray_multiblock_fast.cu使用共享内存以改进内核的性能,而reverseArray_multiblock.cu在全局内存里执行。尝试下给这两个程序计时,验证下它们的性能区别。另外,reverseArray_multiblock.cu 访问全局内存的效率不高。在以后的专栏文章里,我们将使用CUDAPROFILER协助诊断和纠正这个性能,并且展示最新的10个系列架构中的改进如何减少了许多情况下对这些优化的需求。
#include <stdio.h>
#include <assert.h>
#include "cuda.h"
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <device_functions.h>
//检查CUDA运行时是否有错误
void checkCUDAError(const char* msg);
// Part 2 of 2: 使用共享内存执行内核
__global__ void reverseArrayBlock(int *d_out, int *d_in)
{
extern __shared__ int s_data[];
int inOffset = blockDim.x * blockIdx.x;
int in = inOffset + threadIdx.x;
// Load one element per thread from device memory and store it
// *in reversed order* into temporary shared memory
/*
每个线程从设备内存加载一个数据元素并按逆序存储在共享存储器上
*/
s_data[blockDim.x - - threadIdx.x] = d_in[in];
/*
阻塞,一直到所有线程将他们的数据都写入到共享内存中
*/
__syncthreads();
// write the data from shared memory in forward order,
// but to the reversed block offset as before
/*
将共享内存中的数据s_data写入到d_out中,按照前序
*/
int outOffset = blockDim.x * (gridDim.x - - blockIdx.x);
int out = outOffset + threadIdx.x;
d_out[out] = s_data[threadIdx.x];
}
////////////////////////////////////////////////////////////////////
//主函数
////////////////////////////////////////////////////////////////////
int main(int argc, char** argv)
{
//指向主机的内存空间和大小
int *h_a;
int dimA = * ; // 256K elements (1MB total)
// pointer for device memory
int *d_b, *d_a;
//指向设备的指针和大小
int numThreadsPerBlock = ; /*
根据数组大小和预设的块大小来计算需要的块数
*/
int numBlocks = dimA / numThreadsPerBlock;
/*
Part 1 of 2:
计算共享内存所需的内存空间大小,这在下面的内核调用时被使用
*/
int sharedMemSize = numThreadsPerBlock * sizeof(int);
//申请主机及设备上的存储空间
size_t memSize = numBlocks * numThreadsPerBlock * sizeof(int);
//主机上的大小
h_a = (int *)malloc(memSize);
//设备上的大小
cudaMalloc((void **)&d_a, memSize);
cudaMalloc((void **)&d_b, memSize);
//在主机上初始化输入数组
for (int i = ; i < dimA; ++i)
{
h_a[i] = i;
}
//将主机数组拷贝到设备上,h_a-->d_a
cudaMemcpy(d_a, h_a, memSize, cudaMemcpyHostToDevice);
//启动内核
dim3 dimGrid(numBlocks);
dim3 dimBlock(numThreadsPerBlock);
reverseArrayBlock << < dimGrid, dimBlock, sharedMemSize >> >(d_b, d_a);
//阻塞,一直到设备完成计算
cudaThreadSynchronize();
//检查是否设备产生了错误
//检查任何CUDA错误
checkCUDAError("kernel invocation");
//将结果从设备拷贝到主机,d_b-->h_a
cudaMemcpy(h_a, d_b, memSize, cudaMemcpyDeviceToHost);
//检查任何CUDA错误
checkCUDAError("memcpy");
//核对返回到主机上的结果是否正确
for (int i = ; i < dimA; i++)
{
assert(h_a[i] == dimA - - i);
}
//释放设备内存
cudaFree(d_a);
cudaFree(d_b);
//释放主机内存
free(h_a);
printf("Correct!\n");
return ;
} void checkCUDAError(const char *msg)
{
cudaError_t err = cudaGetLastError();
if (cudaSuccess != err)
{
fprintf(stderr, "Cuda error: %s: %s.\n", msg, cudaGetErrorString(err));
exit(EXIT_FAILURE);
}
}
确定运行时共享内存的量需要在主机和设备代码里进行设置。在本示例中,内核中每个块的共享内存的量(字节为单位)在主机上的执行配置中予以明确,作为可选的第三个参数。(仅当共享内存的容量在内核启动时指定后,才设置主机端。如果在编译时修复,则主机端不需要任何设置。)例如,在主机代码arrayReversal_multiblock_fast.cu中,以下代码片段为一个整数数组(包含的元素数等于数据块中的线程数)分配共享内存:
// Part 1 of 2: Compute the number of bytes of share memory needed
// This is used in the kernel invocation below
int sharedMemSize = numThreadsPerBlock * sizeof(int);
检测reverseArrayBlock 内核, 共享内存使用以下代码声明:
extern __shared__ int s_data[]; extern __shared__ int s_data[];
注意:内核中没有指定大小-大小通过执行配置从主机获取。
在下一个关于配置的专栏前,我建议研究一下reverseArray_multiblock.cu。您认为访问全局内存时存在性能问题吗?如果您认为存在,请尝试解决这一问题。
CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第五节的更多相关文章
- CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第七节
第七节:使用下一代CUDA硬件,快乐加速度 原文链接 Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Laboratory)的高级科研人员.他在多个 ...
- CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第六节
原文链接 第六节:全局内存和CUDA RPOFILER Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Laboratory)的高级科研人员.他在 ...
- CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第四节
了解和使用共享内存(1) Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Laboratory)的高级科研人员.他在多个国家级的实验室进行大型并行运 ...
- CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第十节
原文链接 第十节:CUDPP, 强大的数据平行CUDA库Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Laboratory)的高级科研人员.他在多 ...
- CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第一节
原文链接 第一节 CUDA 让你可以一边使用熟悉的编程概念,一边开发可在GPU上运行的软件. Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Lab ...
- CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第八节
原文链接 第八节:利用CUDA函数库 Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Laboratory)的高级科研人员.他在多个国家级的实验室进 ...
- CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第二节
原文链接 第二节:第一个内核 Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Laboratory)的高级科研人员.他在多个国家级的实验室进行大型并 ...
- CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第九节
原文链接 第九节:使用CUDA拓展高等级语言 Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Laboratory)的高级科研人员.他在多个国家级的 ...
- CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第三节
原文链接 第三节:错误处理和全局内存性能局限 恭喜!通过对CUDA(Compute Unified DeviceArchitecture,即计算统一设备架构的首字母缩写)系列文章第一节和第二节,您现在 ...
随机推荐
- 帝都Day6——图论
//P2O5呢? 一.图的存储: 邻接矩阵:邻接表. 邻接矩阵:n*n的[][],[i][j]节点有边记1没边0 缺点 空间复杂度O(n^2) 占用内存较大(我为什么要把这些东西写到这里呢???) 邻 ...
- 最大加权矩形 压缩+前缀和+dp
题目描述 为了更好的备战NOIP2013,电脑组的几个女孩子LYQ,ZSC,ZHQ认为,我们不光需要机房,我们还需要运动,于是就决定找校长申请一块电脑组的课余运动场地,听说她们都是电脑组的高手,校长没 ...
- 低价购买 dp
题目描述 “低价购买”这条建议是在奶牛股票市场取得成功的一半规则.要想被认为是伟大的投资者,你必须遵循以下的问题建议:“低价购买:再低价购买”.每次你购买一支股票,你必须用低于你上次购买它的价格购买它 ...
- 洛谷P2826 LJJ的数学课
题目背景 题目描述(本题是提高组第二题难度+) 题目描述 \(LJJ\)又要开始上数学课啦!(\(T1\),永恒不变的数学) \(LJJ\)的\(Teacher\)对上次的考试很不满意(其实是出题人对 ...
- 记录一下今天犯得错误,public static function init()写成了public function initialize()
tp5模型事件是放在函数 public static function init() 而我写成了初始化函数,编辑器生成的 public function initialize() 开始时用着没出问题, ...
- day02笔记
1.linux环境配置阿里云yum源 linux软件包管理之 yum工具(如同pip3工具) pip3是管理python模块的工具,自动解决模块依赖,降低开发人员心智负担 pip3 install f ...
- 华东交通大学2017年ACM“双基”程序设计竞赛 1002
Problem Description 一天YZW参加了学校组织交际舞活动,活动的开始活动方分别给男生和女生从1-n进行编号,按照从小到大顺时针的方式进行男女搭档分配,相同编号的男女组合成一对,例如一 ...
- Codeforces Round #365 (Div. 2) B
Description Little Mishka is a great traveller and she visited many countries. After thinking about ...
- Django的用户认证组件,自定义分页
一.用户认证组件 1.auth模块 from django.conrtrib import auth django.contrib.auth中提供了许多方法,这里主要介绍其中的三个: 1)authen ...
- java中存在垃圾回收机制,但是还会有内存泄漏的问题,原因是
答案是肯定的,但不能拿这一句回答面试官的问题.分析:JAVA是支持垃圾回收机制的,在这样的一个背景下,内存泄露又被称为“无意识的对象保持”.如果一个对象引用被无意识地保留下来,那么垃圾回收器不仅不会处 ...