首页 技术 正文
技术 2022年11月21日
0 收藏 608 点赞 4,141 浏览 4512 个字

  为什么要使用共享内存呢,因为共享内存的访问速度快。这是首先要明确的,下面详细研究。

  cuda程序中的内存使用分为主机内存(host memory) 和 设备内存(device memory),我们在这里关注的是设备内存。设备内存都位于gpu之上,前面我们看到在计算开始之前,每次我们都要在device上申请内存空间,然后把host上的数据传入device内存。cudaMalloc()申请的内存,还有在核函数中用正常方法申请的变量的内存。这些内存叫做全局内存,那么还有没有别的内存种类呢?常用的还有共享内存,常量内存,纹理内存,他们都用一些不正常的方法申请。

  他们的申请方法如下:

  共享内存:__shared__  变量类型 变量名;

  常量内存:__constant__ 变量类型 变量名;

  纹理内存:texture<变量类型> 变量名;

 
存储类型 寄存器 共享内存 纹理内存 常量内存 全局内存
带宽 ~8TB/s ~1.5TB/s ~200MB/s ~200MB/s

~200MB/s

延迟 1个周期 1~32周期 400~600周期 400~600周期 400~600周期

  他们在不同的情况下有各自的作用,他们最大的区别就是带宽不同,通俗说就是访问速度不同。后面三个看起来没什么不同,但是他们在物理结构方面有差别,适用于不同的情况。

  共享内存实际上是可受用户控制的一级缓存。申请共享内存后,其内容在每一个用到的block被复制一遍,使得在每个block内,每一个thread都可以访问和操作这块内存,而无法访问其他block内的共享内存。这种机制就使得一个block之内的所有线程可以互相交流和合作。下面的例子中就显示了线程之间的交流和合作。

  这个例子计算的是两个向量的点积。

 /*
* Copyright 1993-2010 NVIDIA Corporation. All rights reserved.
*
* NVIDIA Corporation and its licensors retain all intellectual property and
* proprietary rights in and to this software and related documentation.
* Any use, reproduction, disclosure, or distribution of this software
* and related documentation without an express license agreement from
* NVIDIA Corporation is strictly prohibited.
*
* Please refer to the applicable NVIDIA end user license agreement (EULA)
* associated with this source code for terms and conditions that govern
* your use of this NVIDIA software.
*
*/ #include "../common/book.h" #define imin(a,b) (a<b?a:b) const int N = * ;
const int threadsPerBlock = ;
const int blocksPerGrid =
imin( , (N+threadsPerBlock-) / threadsPerBlock ); __global__ void dot( float *a, float *b, float *c ) {
__shared__ float cache[threadsPerBlock];
int tid = threadIdx.x + blockIdx.x * blockDim.x;
int cacheIndex = threadIdx.x; float temp = ;
while (tid < N) {
temp += a[tid] * b[tid];
tid += blockDim.x * gridDim.x;
} // set the cache values
cache[cacheIndex] = temp; // synchronize threads in this block
__syncthreads(); // for reductions, threadsPerBlock must be a power of 2
// because of the following code
int i = blockDim.x/;
while (i != ) {
if (cacheIndex < i)
cache[cacheIndex] += cache[cacheIndex + i];
__syncthreads();
i /= ;
} if (cacheIndex == )
c[blockIdx.x] = cache[];
} int main( void ) {
float *a, *b, c, *partial_c;
float *dev_a, *dev_b, *dev_partial_c; // allocate memory on the cpu side
a = (float*)malloc( N*sizeof(float) );
b = (float*)malloc( N*sizeof(float) );
partial_c = (float*)malloc( blocksPerGrid*sizeof(float) ); // allocate the memory on the GPU
HANDLE_ERROR( cudaMalloc( (void**)&dev_a,
N*sizeof(float) ) );
HANDLE_ERROR( cudaMalloc( (void**)&dev_b,
N*sizeof(float) ) );
HANDLE_ERROR( cudaMalloc( (void**)&dev_partial_c,
blocksPerGrid*sizeof(float) ) ); // fill in the host memory with data
for (int i=; i<N; i++) {
a[i] = i;
b[i] = i*;
} // copy the arrays 'a' and 'b' to the GPU
HANDLE_ERROR( cudaMemcpy( dev_a, a, N*sizeof(float),
cudaMemcpyHostToDevice ) );
HANDLE_ERROR( cudaMemcpy( dev_b, b, N*sizeof(float),
cudaMemcpyHostToDevice ) ); dot<<<blocksPerGrid,threadsPerBlock>>>( dev_a, dev_b,
dev_partial_c ); // copy the array 'c' back from the GPU to the CPU
HANDLE_ERROR( cudaMemcpy( partial_c, dev_partial_c,
blocksPerGrid*sizeof(float),
cudaMemcpyDeviceToHost ) ); // finish up on the CPU side
c = ;
for (int i=; i<blocksPerGrid; i++) {
c += partial_c[i];
} #define sum_squares(x) (x*(x+1)*(2*x+1)/6)
printf( "Does GPU value %.6g = %.6g?\n", c,
* sum_squares( (float)(N - ) ) ); // free memory on the gpu side
HANDLE_ERROR( cudaFree( dev_a ) );
HANDLE_ERROR( cudaFree( dev_b ) );
HANDLE_ERROR( cudaFree( dev_partial_c ) ); // free memory on the cpu side
free( a );
free( b );
free( partial_c );
}

  我们首先关注核函数dot。__shared__ float cache[threadsPerBlock];就是这节重点,申请cache数组时,由于使用了共享内存,则每一个block里面都有一份cache,使得block内的thread都可以访问和操作其各自的cache数组。

 while (tid < N) {
temp += a[tid] * b[tid];
tid += blockDim.x * gridDim.x;
}

这一段我们相当熟悉,每个线程计算若干对a,b的乘积,然后相加。然后这样cache[cacheIndex] = temp;将结果存入cache中。这时,每一个线程的结果都被存在了cache数组中,我们知道接下来要对数组求和,然而这里有潜在的危险,那就是我们不知道所有线程是否已经将数据写入了cache,也就是说,是否每一个线程都已经执行完了第39行。这里就需要等待,等待所有线程执行到同一位置,这就是 __syncthreads();的作用。这个函数称为同步函数,即在所有线程全部执行到__syncthreads()为止,谁也不许动,其后任何代码都无法执行。

  因此,我们可以很清楚的明白所有线程全部执行完了第39行,然后同步解除,大家再一起往前走。做加法。

 int i = blockDim.x/;
while (i != ) {
if (cacheIndex < i)
cache[cacheIndex] += cache[cacheIndex + i];
__syncthreads();
i /= ;
} if (cacheIndex == )
c[blockIdx.x] = cache[];

  这段就不难理解了,逐对相加,最后cache【0】位置的数就是结果。将其值存入c数组,准备导出。

剩下的main函数部分是如下几步操作(和前面学习的差不多):

1.为输入输出数组分配内存

2.将a,b数组付初值,然后复制给device中,cudaMemcpy()

3.调用核函数执行并行计算。

4.device值返回后数组c求和。

  很明显,由于我们使用了共享内存存储cache数组,使得在操作cache数组时的速度有了大幅提高(相比于全局内存)。共享内存的意义也就在此。

现在,请观察下面的两组代码:

 while (i != ) {
if (cacheIndex < i)
cache[cacheIndex] += cache[cacheIndex + i];
__syncthreads();
i /= ;
}
 while (i != ) {
if (cacheIndex < i)
{
cache[cacheIndex] += cache[cacheIndex + i];
__syncthreads();
}
i /= ;
}

下面的代码中由于if的存在,只有部分线程包含同步操作。代码似乎得到了优化。但是真的如此吗

当然不是的,上面的红字“所有线程全部执行到__syncthreads()为止”,所有很重要,<<<>>>中launch了多少个threadperblock,那么就必须要等待所有的线程,一个都不能少。由于if的存在,上例中部分线程永远都不可能执行到cache[cacheIndex] += cache[cacheIndex + i];这一步,因此就要永远等待下去,因而程序无法执行。

总结:在能用共享内存的时候尽量用,进而提高block内的执行效率,但是在同步问题上一定要慎重。。。

  

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,028
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,518
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,365
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,146
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,780
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,857