cuda学习3-共享内存和同步
为什么要使用共享内存呢,因为共享内存的访问速度快。这是首先要明确的,下面详细研究。
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内的执行效率,但是在同步问题上一定要慎重。。。
cuda学习3-共享内存和同步的更多相关文章
- CUDA: 共享内存与同步
CUDA C支持共享内存, 将CUDA C关键字__shared__添加到变量声明中,将使这个变量驻留在共享内存中.对在GPU上启动的每个线程块,CUDA C编译器都将创建该变量的一个副本.线程块中的 ...
- Linux学习日志--共享内存
一:什么是共享内存 共享内存是属于IPC(Inter-Process Communication进程间通信)机制,其它两种是信号量和消息队列,该机制为进程开辟创建了特殊的地址范 ...
- linux 进程学习笔记-共享内存
如果能划定一块物理内存,让多个进程都能将该内存映射到其自身虚拟内存空间的话,那么进程可以通过向这块内存空间读写数据而达到通信的目的.另外,和消息队列不同的是,共享的内存在用户空间而不是核空间,那么就不 ...
- ubuntu linux c学习笔记----共享内存(shmget,shmat,shmdt,shmctl)
shmget int shmget(key_t key, size_t size, int flag); key: 标识符的规则 size:共享存储段的字节数 flag:读写的权限 返回值:成功返回共 ...
- linux 共享内存 信号量 同步
这篇文章将讲述别一种进程间通信的机制——信号量.注意请不要把它与之前所说的信号混淆起来,信号与信号量是不同的两种事物.有关信号的更多内容,可以阅读我的另一篇文章:Linux进程间通信——使用信号.下面 ...
- 信号量学习 & 共享内存同步
刚刚这篇文章学习了共享内存:http://www.cnblogs.com/charlesblc/p/6142139.html 里面也提到了共享内存,自己不进行同步,需要其他手段比如信号量来进行.那么现 ...
- 共享内存mmap学习 及与 shmxxx操作的区别
上一篇学习了共享内存: http://www.cnblogs.com/charlesblc/p/6142139.html 根据这个 http://blog.chinaunix.net/uid-2633 ...
- linux实现共享内存同步的四种方法
https://blog.csdn.net/sunxiaopengsun/article/details/79869115 本文主要对实现共享内存同步的四种方法进行了介绍. 共享内存是一种最为高效的进 ...
- linux io 学习笔记(03)---共享内存,信号灯,消息队列
system V IPC 1)消息队列 2)共享内存 3)信号灯(信号量集) 1.消息队列. ipcs -q 查看系统中使用消息队列的情况 ipcrm -q +msqid 删除消息队列 消息队列工作原 ...
随机推荐
- ios 关于时间戳与时间转化的笔记
linux系统获取时间戳的方法:time() ; 时间戳转换成需要的时间格式: NSDateFormatter *formatter=[[NSDateFormatter alloc]init]; [f ...
- Java NIO之通道
一.前言 前面学习了缓冲区的相关知识点,接下来学习通道. 二.通道 2.1 层次结构图 对于通道的类层次结构如下图所示. 其中,Channel是所有类的父类,其定义了通道的基本操作.从 Channel ...
- Jquery对复选框CheckBox的操作
checkbox: 多选框 //获取选中值 checkbox:$("#checkbox_id").attr("value"): 多选框checkbox,打勾: ...
- extj6.0写增删查改(1)-------查询
本文主要实现的效果是:点击查询按钮,根据form中的条件,在Grid中显示对应的数据(如果form为空,显示全部数据) 一.静态页面 1.查询按钮 { text:'查询', handler: 'onS ...
- 树型权限管理插件:jQuery Tree Multiselect详细使用指南
1.认识jQuery Tree Multiselect 这个插件允许用户以树型的形式来呈现列表复选框的选择.多用于权限管理中用于分配不同的权限.使用文档,请参考: https://github ...
- 页面性能优化的利器 — Timeline
欢迎大家关注腾讯云技术社区-博客园官方主页,我们将持续在博客园为大家推荐技术精品文章哦~ 陈泽钦,腾讯移动客户端工程师,目前就职于腾讯MIG移动互联网事业群,负责腾讯浏览服务TBS的X5内核业务. 1 ...
- codeforces 803C Maximal GCD(GCD数学)
Maximal GCD 题目链接:http://codeforces.com/contest/803/problem/C 题目大意: 给你n,k(1<=n,k<=1e10). 要你输出k个 ...
- 在android中,如何去掉webview读取网页后点击网页上的按钮出现的方框
参考:http://blog.sina.com.cn/s/blog_64056edc0100xt3l.html <style type="text/css"> .bor ...
- python-day 练习1
#!/usr/bin/env python# -*- coding:utf-8 -*-'''需求: a. 元素分类 有如下值集合 v1 = [11,22,33,44,55,66,77,88,99,90 ...
- sysbench压测mysql
MySQL数据库测试 select 1.先创建数据库test,再准备数据 time /usr/local/sysbench/bin/sysbench --test=oltp --num-threa ...