CUDA 程序中的同步

前言

  在并发,多线程环境下,同步是一个很重要的环节。同步即是指进程/线程之间的执行顺序约定。

  本文将介绍如何通过共享内存机制实现块内多线程之间的同步。

  至于块之间的同步,需要使用到 global memory,代价较为高昂,目前使用的情况也不多,就先不介绍了。

块内同步函数:__syncthreads
()

  线程调用此函数后,该线程所属块中的所有线程均运行到这个调用点后才会继续往下运行。

代码示例

  使用同步思想优化之前一篇博文中提到的数组求和程序。在新的程序中,让每个块中的第一个线程将块中所有线程的运算结果都加起来,然后再存入到结果数组中。这样,结果数组的长度与块数相等
(原来是和总线程数相等),大大降低了 CPU 端程序求和的工作量以及需要传递进/出显存的数据 (代码下方如果出现红色波浪线无视之):


  1 // 相关 CUDA 库
2 #include "cuda_runtime.h"
3 #include "cuda.h"
4 #include "device_launch_parameters.h"
5
6 // 此头文件包含 __syncthreads ()函数
7 #include "device_functions.h"
8
9 #include <iostream>
10 #include <cstdlib>
11
12 using namespace std;
13
14 const int N = 100;
15
16 // 块数
17 const int BLOCK_data = 3;
18 // 各块中的线程数
19 const int THREAD_data = 10;
20
21 // CUDA初始化函数
22 bool InitCUDA()
23 {
24 int deviceCount;
25
26 // 获取显示设备数
27 cudaGetDeviceCount (&deviceCount);
28
29 if (deviceCount == 0)
30 {
31 cout << "找不到设备" << endl;
32 return EXIT_FAILURE;
33 }
34
35 int i;
36 for (i=0; i<deviceCount; i++)
37 {
38 cudaDeviceProp prop;
39 if (cudaGetDeviceProperties(&prop,i)==cudaSuccess) // 获取设备属性
40 {
41 if (prop.major>=1) //cuda计算能力
42 {
43 break;
44 }
45 }
46 }
47
48 if (i==deviceCount)
49 {
50 cout << "找不到支持 CUDA 计算的设备" << endl;
51 return EXIT_FAILURE;
52 }
53
54 cudaSetDevice(i); // 选定使用的显示设备
55
56 return EXIT_SUCCESS;
57 }
58
59 // 此函数在主机端调用,设备端执行。
60 __global__
61 static void Sum (int *data,int *result)
62 {
63 // 声明共享内存 (数组)
64 extern __shared__ int shared[];
65 // 取得线程号
66 const int tid = threadIdx.x;
67 // 获得块号
68 const int bid = blockIdx.x;
69
70 shared[tid] = 0;
71 // 有点像网格计算的思路
72 for (int i=bid*THREAD_data+tid; i<N; i+=BLOCK_data*THREAD_data)
73 {
74 shared[tid] += data[i];
75 }
76
77 // 块内线程同步函数
78 __syncthreads ();
79
80 // 每个块内索引为 0 的线程对其组内所有线程的求和结果再次求和
81 if (tid == 0) {
82 for(int i = 1; i < THREAD_data; i++) {
83 shared[0] += shared[i];
84 }
85 // result 数组存放各个块的计算结果
86 result[bid] = shared[0];
87 }
88 }
89
90 int main ()
91 {
92 // 初始化 CUDA 编译环境
93 if (InitCUDA()) {
94 return EXIT_FAILURE;
95 }
96 cout << "成功建立 CUDA 计算环境" << endl << endl;
97
98 // 建立,初始化,打印测试数组
99 int *data = new int [N];
100 cout << "测试矩阵: " << endl;
101 for (int i=0; i<N; i++)
102 {
103 data[i] = rand()%10;
104 cout << data[i] << " ";
105 if ((i+1)%10 == 0) cout << endl;
106 }
107 cout << endl;
108
109 int *gpudata, *result;
110
111 // 在显存中为计算对象开辟空间
112 cudaMalloc ((void**)&gpudata, sizeof(int)*N);
113 // 在显存中为结果对象开辟空间
114 cudaMalloc ((void**)&result, sizeof(int)*BLOCK_data);
115
116 // 将数组数据传输进显存
117 cudaMemcpy (gpudata, data, sizeof(int)*N, cudaMemcpyHostToDevice);
118 // 调用 kernel 函数 - 此函数可以根据显存地址以及自身的块号,线程号处理数据。
119 Sum<<<BLOCK_data,THREAD_data,THREAD_data*sizeof (int)>>> (gpudata,result);
120
121 // 在内存中为计算对象开辟空间
122 int *sumArray = new int[BLOCK_data];
123 // 从显存获取处理的结果
124 cudaMemcpy (sumArray, result, sizeof(int)*BLOCK_data, cudaMemcpyDeviceToHost);
125
126 // 释放显存
127 cudaFree (gpudata);
128 cudaFree (result);
129
130 // 计算 GPU 每个块计算出来和的总和
131 int final_sum=0;
132 for (int i=0; i<BLOCK_data; i++)
133 {
134 final_sum += sumArray[i];
135 }
136
137 cout << "GPU 求和结果为: " << final_sum << endl;
138
139 // 使用 CPU 对矩阵进行求和并将结果对照
140 final_sum = 0;
141 for (int i=0; i<N; i++)
142 {
143 final_sum += data[i];
144 }
145 cout << "CPU 求和结果为: " << final_sum << endl;
146
147 getchar();
148
149 return 0;
150 }

运行结果

  

小结

  共享内存,或者说这个共享数组是 CUDA 中实现同步最常用的方法。

时间: 2024-10-07 15:09:39

CUDA 程序中的同步的相关文章

CUDA block 中的同步,fence 和原子操作

一直纠结在fence 和 原子操作. 记住:原子操作是当多个thread 准备对同一个数据进行写操作,原子操作的目的是保证该数据只被一个thread 读.修改.写.这三个步骤不会受其他thread影响. fence 是保证thread 对 数据的修改被其他thread发现,发现后,该thread 继续向前走. __syncthreads, 保证所有thread 同时到达一个点,然后继续前进.

CUDA程序编译过程中产生警告的解决方法

有时候经常使用别人用Tabhost+其它的实现demo.单纯利用Tabhost该如何使用呢? 下面看例子: public class MainActivity extends TabActivity { public TabHost tabHost; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 获取对象 tabHost = getTabH

第三章--Win32程序的执行单元(部分概念及代码讲解)(中-线程同步

学习<Windows程序设计>记录 概念贴士: 1. 同步可以保证在一个时间内只有一个线程对其共享资源有控制权.PS:共享资源包括全局变量.公共数据成员或者句柄等. 2. 临界区内核对象和时间内核对象可以很好地用于多线程同步和它们之间的通信. 3. 线程同步必要性:当多个线程在同一个进程中执行时,可能有不止一个线程同时执行同一段代码,访问同一段内存中的数据.多个线程同时读取共享数据没有问题,但是如果同时读写,情况就不同,也许会产生极大的错误.(如:程序CountErr).解决同步问题,就是保证

【转】CUDA程序优化要点

CUDA程序优化应该考虑的点:精度:只在关键步骤使用双精度,其他部分仍然使用单精度浮点以获得指令吞吐量和精度的平衡: 目前 GPU 的单精度性能要远远超过双精度性能,整数乘法.求模.求余等运算的指令吞吐量也较为有限.在科学计算中,由于需要处理的数据量巨大,往往采用双精度或者四精度才能获得可靠的结果,目前的 Tesla 架构还不能很好的满足高精度计算的需要.如果你的计算需要很高的精度,或者需要进行很多轮的迭代,最好考虑在关键的步骤中使用双精度,而在其他部分仍然使用单精度浮点以获得指令吞吐量和精度的

快速开发CUDA程序的方法

根据几年的CUDA开发经验,简单的介绍下CUDA程序的大概开发步骤,按照先修改CPU串行程序后移植到GPU平台的原理,把需要在GPU上做的工作尽量先在CPU平台上修改,降低了程序的开发难度,同时有利用bug的调试.通过实现一种快速.有效地CUDA并行程序开发的方法,提高CUDA并行程序开发效率,降低CUDA并行程序开发周期和难度. (1)    CPU串行程序分析 对于CPU串行程序,首先需要测试串行程序中的热点函数,以及分析热点函数的并行性: a)       热点测试 根据时间的测试结果确定

React中setState同步更新策略

本文和大家分享的主要是React中setState同步更新相关内容,希望对大家学习React有所帮助. 为了提高性能React将setState设置为批次更新,即是异步操作函数,并不能以顺序控制流的方式设置某些事件,我们也不能依赖于 this.state 来计算未来状态.典型的譬如我们希望在从服务端抓取数据并且渲染到界面之后,再隐藏加载进度条或者外部加载提示: componentDidMount() { fetch('https://example.com') .then((res) => re

解决CUDA程序的黑屏恢复问题

本文引用自 http://blog.163.com/yuhua_kui/blog/static/9679964420146183211348/ 问题描述: 在运行CUDA程序时,出现黑屏,过一会儿屏幕恢复之后,出现如下界面:<显卡挂掉了 > ============================================================================== 解决方案:  调整计算机的TDR值    Timeout Detection & Reco

如何在程序中调用Caffe做图像分类

Caffe是目前深度学习比较优秀好用的一个开源库,采样c++和CUDA实现,具有速度快,模型定义方便等优点.学习了几天过后,发现也有一个不方便的地方,就是在我的程序中调用Caffe做图像分类没有直接的接口.Caffe的数据层可以从数据库(支持leveldb.lmdb.hdf5).图片.和内存中读入.我们要在程序中使用,当然得从内存中读入.参见http://caffe.berkeleyvision.org/tutorial/layers.html#data-layers和MemoryDataLay

《java并发编程实战》读书笔记4--基础构建模块,java中的同步容器类&amp;并发容器类&amp;同步工具类,消费者模式

上一章说道委托是创建线程安全类的一个最有效策略,只需让现有的线程安全的类管理所有的状态即可.那么这章便说的是怎么利用java平台类库的并发基础构建模块呢? 5.1 同步容器类 包括Vector和Hashtable,此外还包括在JDK1.2中添加的一些功能相似的类,这些同步的封装器类由Collections.synchronizedXxx等工厂方法创建的.这些类实现线程安全的方式是:将他们的状态封装起来,并对每个共有方法都进行同步,使得每次只能有一个线程能访问容器的状态. 关于java中的Vect