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之内的所有线程可以互相交流和合作。下面的例子中就显示了线程之间的交流和合作。

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

  1 /*
  2  * Copyright 1993-2010 NVIDIA Corporation.  All rights reserved.
  3  *
  4  * NVIDIA Corporation and its licensors retain all intellectual property and
  5  * proprietary rights in and to this software and related documentation.
  6  * Any use, reproduction, disclosure, or distribution of this software
  7  * and related documentation without an express license agreement from
  8  * NVIDIA Corporation is strictly prohibited.
  9  *
 10  * Please refer to the applicable NVIDIA end user license agreement (EULA)
 11  * associated with this source code for terms and conditions that govern
 12  * your use of this NVIDIA software.
 13  *
 14  */
 15
 16
 17 #include "../common/book.h"
 18
 19 #define imin(a,b) (a<b?a:b)
 20
 21 const int N = 33 * 1024;
 22 const int threadsPerBlock = 256;
 23 const int blocksPerGrid =
 24             imin( 32, (N+threadsPerBlock-1) / threadsPerBlock );
 25
 26
 27 __global__ void dot( float *a, float *b, float *c ) {
 28     __shared__ float cache[threadsPerBlock];
 29     int tid = threadIdx.x + blockIdx.x * blockDim.x;
 30     int cacheIndex = threadIdx.x;
 31
 32     float   temp = 0;
 33     while (tid < N) {
 34         temp += a[tid] * b[tid];
 35         tid += blockDim.x * gridDim.x;
 36     }
 37
 38     // set the cache values
 39     cache[cacheIndex] = temp;
 40
 41     // synchronize threads in this block
 42     __syncthreads();
 43
 44     // for reductions, threadsPerBlock must be a power of 2
 45     // because of the following code
 46     int i = blockDim.x/2;
 47     while (i != 0) {
 48         if (cacheIndex < i)
 49             cache[cacheIndex] += cache[cacheIndex + i];
 50         __syncthreads();
 51         i /= 2;
 52     }
 53
 54     if (cacheIndex == 0)
 55         c[blockIdx.x] = cache[0];
 56 }
 57
 58
 59 int main( void ) {
 60     float   *a, *b, c, *partial_c;
 61     float   *dev_a, *dev_b, *dev_partial_c;
 62
 63     // allocate memory on the cpu side
 64     a = (float*)malloc( N*sizeof(float) );
 65     b = (float*)malloc( N*sizeof(float) );
 66     partial_c = (float*)malloc( blocksPerGrid*sizeof(float) );
 67
 68     // allocate the memory on the GPU
 69     HANDLE_ERROR( cudaMalloc( (void**)&dev_a,
 70                               N*sizeof(float) ) );
 71     HANDLE_ERROR( cudaMalloc( (void**)&dev_b,
 72                               N*sizeof(float) ) );
 73     HANDLE_ERROR( cudaMalloc( (void**)&dev_partial_c,
 74                               blocksPerGrid*sizeof(float) ) );
 75
 76     // fill in the host memory with data
 77     for (int i=0; i<N; i++) {
 78         a[i] = i;
 79         b[i] = i*2;
 80     }
 81
 82     // copy the arrays ‘a‘ and ‘b‘ to the GPU
 83     HANDLE_ERROR( cudaMemcpy( dev_a, a, N*sizeof(float),
 84                               cudaMemcpyHostToDevice ) );
 85     HANDLE_ERROR( cudaMemcpy( dev_b, b, N*sizeof(float),
 86                               cudaMemcpyHostToDevice ) );
 87
 88     dot<<<blocksPerGrid,threadsPerBlock>>>( dev_a, dev_b,
 89                                             dev_partial_c );
 90
 91     // copy the array ‘c‘ back from the GPU to the CPU
 92     HANDLE_ERROR( cudaMemcpy( partial_c, dev_partial_c,
 93                               blocksPerGrid*sizeof(float),
 94                               cudaMemcpyDeviceToHost ) );
 95
 96     // finish up on the CPU side
 97     c = 0;
 98     for (int i=0; i<blocksPerGrid; i++) {
 99         c += partial_c[i];
100     }
101
102     #define sum_squares(x)  (x*(x+1)*(2*x+1)/6)
103     printf( "Does GPU value %.6g = %.6g?\n", c,
104              2 * sum_squares( (float)(N - 1) ) );
105
106     // free memory on the gpu side
107     HANDLE_ERROR( cudaFree( dev_a ) );
108     HANDLE_ERROR( cudaFree( dev_b ) );
109     HANDLE_ERROR( cudaFree( dev_partial_c ) );
110
111     // free memory on the cpu side
112     free( a );
113     free( b );
114     free( partial_c );
115 }

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

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

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

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

 1 int i = blockDim.x/2;
 2     while (i != 0) {
 3         if (cacheIndex < i)
 4             cache[cacheIndex] += cache[cacheIndex + i];
 5         __syncthreads();
 6         i /= 2;
 7     }
 8
 9     if (cacheIndex == 0)
10         c[blockIdx.x] = cache[0];

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

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

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

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

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

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

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

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

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

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

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

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

  

时间: 2024-10-07 16:55:12

cuda学习3-共享内存和同步的相关文章

CUDA学习5 常量内存与事件

当线程束中的所有线程都访问相同的只读数据时,使用常量内存将获得额外的性能提升. 常量内存大小限制为64k. 以下摘自hackairM的博文CUDA学习--内存处理之常量内存(4). 常量内存其实只是全局内存的一种虚拟地址形式,并没有特殊保留的常量内存块.常量内存有两个特性,一个是高速缓存,另一个是它支持将单个值广播到线程束中的每个线程.但要注意的是,对于那些数据不太集中或者数据重用率不高的内存访问,尽量不要使用常量内存. 当常量内存将数据分配或广播到线程束中的每个线程时(注意,实际上硬件会将单次

linux 进程学习笔记-共享内存

如果能划定一块物理内存,让多个进程都能将该内存映射到其自身虚拟内存空间的话,那么进程可以通过向这块内存空间读写数据而达到通信的目的.另外,和消息队列不同的是,共享的内存在用户空间而不是核空间,那么就不存在“用户空间和内核空间之间数据复制”的问题,这会减少不少开销. 由于不同进程都可能向同一个空间读写数据,所以其需要一些同步机制来防止混乱,可以使用的机制有“信号量”“文件锁”等. 共享内存有mmap和System V Shared Memory, 下面说的是后者. 创建或打开共享内存: int s

Linux学习日志--共享内存

一:什么是共享内存 共享内存是属于IPC(Inter-Process Communication进程间通信)机制,其他两种是信号量和消息队列,该机制为进程开辟创建了特殊的地址范围,就像malloc分配那样.进程可以将同一段共享内存连接到自己的地址空间上,从而操作共享内存,所以说,共享内存提供了多个进程之间共享和传递数据一种方式.需要注意的是:该机制没有提供同步机制,所以我们需要采取有效的机制来同步对共享内存的访问. 二:共享内存的原理图      三: 相关的函数 头文件: #include <

信号量学习 &amp; 共享内存同步

刚刚这篇文章学习了共享内存:http://www.cnblogs.com/charlesblc/p/6142139.html 里面也提到了共享内存,自己不进行同步,需要其他手段比如信号量来进行.那么现在就学习信号量咯. 共享内存实际编程中, 应该使用信号量, 或通过传递消息(使用管道或IPC消息), 或生成信号 的方法来提供读写之间的更有效的同步机制. 方法一.利用POSIX有名信号灯实现共享内存的同步 方法二.利用POSIX无名信号灯实现共享内存的同步 方法三.利用System V的信号灯实现

共享内存mmap学习 及与 shmxxx操作的区别

上一篇学习了共享内存: http://www.cnblogs.com/charlesblc/p/6142139.html 根据这个 http://blog.chinaunix.net/uid-26335251-id-3493125.html 再来一篇: 1. 共享内存允许两个或多个进程共享一给定的存储区,因为数据不需要来回复制,所以是最快的一种进程间通信机制.共享内存可以通过mmap()映射普通文件(特殊情况下还可以采用匿名映射)机制实现,也可以通过系统V共享内存机制实现. 应用接口和原理很简单

linux 实现共享内存同步

本文主要对实现共享内存同步的四种方法进行了介绍. 共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝.它是IPC对象的一种. 为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间.进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率. 同步(synchronization)指的是多个任务(线程)按照约定的顺序相互配合完成一件事情.由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号

CUDA学习日志:线程协作与例程

接触CUDA的时间并不长,最开始是在cuda-convnet的代码中接触CUDA代码,当时确实看的比较痛苦.最近得空,在图书馆借了本<GPU高性能编程 CUDA实战>来看看,同时也整理一些博客来加强学习效果. Jeremy Lin 在上篇博文中,我们已经用CUDA C编写了一个程序,知道了如何编写在GPU上并行执行的代码.但是对于并行编程来说,最重要的一个方面就是,并行执行的各个部分如何通过相互协作来解决问题.只有在极少数情况下,各个处理器才不需要了解其他处理器的执行状态而彼此独立地计算出结果

linux进程间的通信(C): 共享内存

一.共享内存介绍 共享内存是三个IPC(Inter-Process Communication)机制中的一个. 它允许两个不相关的进程访问同一个逻辑内存. 共享内存是在两个正在进行的进程之间传递数据的一种非常有效的方式. 大多数的共享内存的实现, 都把由不同进程之间共享的内存安排为同一段物理内存. 共享内存是由IPC为进程创建一个特殊的地址范围, 它将出现在该进程的地址空间中. 其他进程可以将同一段共享内存连接它们自己的地址空间中. 所有进程都可以访问共享内存中的地址, 就好像它们是由mallo

Linux IPC 共享内存用法

Linux IPC 常见的方式 写 Linux Server 端程序,必然会涉及到进程间通信 IPC. 通信必然伴随着同步机制,下面是一些常见的通信与同步机制: 进程间通信:匿名管道,命名管道,消息队列,共享内存,Domain Socket, 本机 TCP Socket,文件 进程间同步:信号,信号量 线程间同步:条件变量,互斥量,读写锁,自旋锁,Barrier. 对于大部分的业务场景,本机 TCP Socket 足以,现在Linux 也对本机 TCP Socket做了很好的优化.而且如果以后需