这一篇我们一起学习一下如何使用CUDA实现并行归约算法。

首先我们要知道什么是并行归约。并行归约(Reduction)是一种很基础的并行算法,简单来说,我们有N个输入数据,使用一个符合结合律的二元操作符作用其上,最终生成1个结果。这个二元操作符可以是求和、取最大、取最小、平方、逻辑与或等等。

我们以求和为例,假设输入如下:

int array[8] =  [3, 1, 7, 0, 4, 1, 6, 3]

在串行的情况下,算法很容易实现,一般我们会使用下面这样的代码。

int sum = 0;
for (int i = 0; i < N; i++)sum += array[i]

但当我们试图进行并行计算时,问题就会变得复杂。

由于加法的交换律和结合律,数组可以以任意顺序求和。所以我们会自然而然产生这样的思路:首先把输入数组划分为更小的数据块,之后用一个线程计算一个数据块的部分和,最后把所有部分和再求和得出最终结果。

依照以上的思路,我们可以按下图这样计算。就上面的输入例子而言,首先需要我们开辟一个8个int的存储空间,图中一行的数据代表我们开辟的存储空间。计算时首先将相邻的两数相加(也称相邻配对),结果写入第一个数的存储空间内。第二轮迭代时我们再将第一次的结果两两相加得出下一级结果,一直重复这个过程最后直到我们得到最终的结果,空白方框里面存储的内容是我们不需要的。这个过程相当于,每一轮迭代后,选取被加数的跨度翻倍,后面我们核函数就是这样实现的。

相邻配对实现并行归约

相比与串行计算,我们只用了3轮迭代就得出了8个数的和,时间复杂度由O(N)变为O(logN)

当然使用归约算法时我们不会只有这么小的输入数组,这时候就要用到线程块了,我们可以把输入数组先划分成很多包含8个int型值的数组,每一个小数组分配给一个线程块,最后再将所有的结果传回主机串行求和。

主函数如下,与我们上一个例程结构类似,先初始化,分配内存,然后运行核函数,最后和CPU对照组对比,检验结果是否正确。我们重点关注分配线程网格和线程块的主干部分。与上面例子里讲的8元素数组不同,实际使用时为了达到高加速比,我们会把输入数组分成更大的块。

这里我们使用一个16M(16777216)大小的输入数组,分成的小数组大小为1K(1024)。所以程序里将block设置为(1024, 1)的大小,每个线程块完成一个小数组的求和。一共需要16K(16384)个线程块,所以我们设置grid为(16384, 1)。每个线程块的求和结果都保存在全局内存里,等所有线程完成后,统一传回主机,在主机里串行求和得到最后的结果。注意这里的block和grid设置并不是最优,只是为了简单,下一篇中我们会进行优化。

int main(int argc,char** argv)
{initDevice(0);//initializationint size = 1 << 24;printf("    with array size %d  ", size);//execution configurationint blocksize = 1024;if (argc > 1){blocksize = atoi(argv[1]);   //从命令行输入设置block大小}dim3 block(blocksize, 1);dim3 grid((size - 1) / block.x + 1, 1);printf("grid %d block %d n", grid.x, block.x);//allocate host memorysize_t bytes = size * sizeof(int);int *idata_host = (int*)malloc(bytes);int *odata_host = (int*)malloc(grid.x * sizeof(int));int * tmp = (int*)malloc(bytes);//initialize the arrayinitialData_int(idata_host, size);memcpy(tmp, idata_host, bytes);double timeStart, timeElaps;int gpu_sum = 0;// device memoryint * idata_dev = NULL;int * odata_dev = NULL;CHECK(cudaMalloc((void**)&idata_dev, bytes));CHECK(cudaMalloc((void**)&odata_dev, grid.x * sizeof(int)));//cpu reduction 对照组int cpu_sum = 0;timeStart = cpuSecond();//cpu_sum = recursiveReduce(tmp, size);for (int i = 0; i < size; i++)cpu_sum += tmp[i];timeElaps = 1000*(cpuSecond() - timeStart);printf("cpu sum:%d n", cpu_sum);printf("cpu reduction elapsed %lf ms cpu_sum: %dn", timeElaps, cpu_sum);//kernel reduceNeighboredCHECK(cudaMemcpy(idata_dev, idata_host, bytes, cudaMemcpyHostToDevice));CHECK(cudaDeviceSynchronize());timeStart = cpuSecond();reduceNeighbored <<<grid, block >>>(idata_dev, odata_dev, size);cudaDeviceSynchronize();cudaMemcpy(odata_host, odata_dev, grid.x * sizeof(int), cudaMemcpyDeviceToHost);gpu_sum = 0;for (int i = 0; i < grid.x; i++)gpu_sum += odata_host[i];   timeElaps = 1000*(cpuSecond() - timeStart);printf("gpu sum:%d n", gpu_sum);printf("gpu reduceNeighbored elapsed %lf ms     <<<grid %d block %d>>>n",timeElaps, grid.x, block.x);// free host memoryfree(idata_host);free(odata_host);CHECK(cudaFree(idata_dev));CHECK(cudaFree(odata_dev));//reset devicecudaDeviceReset();//check the resultsif (gpu_sum == cpu_sum){printf("Test success!n");}return EXIT_SUCCESS;
}

核函数具体编程实现如下。我们利用一个stride变量,实现不同轮数时被加数的选择。每当计算一轮后,选取被加数的跨度会乘2。在这里,有心的同学就会发现了,第一轮计算中,其实只有一半的线程是活跃的,而且每进行一轮计算后,活跃的线程数都会减少一半,这是条件表达式的使用造成的。比如在第一轮迭代时,只有偶数ID的线程会为True,其主体才能得到执行。这会导致线程束的分化,也就是说只有一部分线程是活跃的,但是所有的线程仍然都会被调度,因为硬件调度线程是以线程束(连续32个线程)为单位进行调度的。这肯定会影响我们程序的执行效率,不过不用太担心,我们下一篇就会提出解决这个问题的方法。

__global__ void reduceNeighbored(int * g_idata,int * g_odata,unsigned int n)
{//set thread IDunsigned int tid = threadIdx.x;//boundary checkif (tid >= n) return;//convert global data pointer to the int *idata = g_idata + blockIdx.x*blockDim.x;//in-place reduction in global memoryfor (int stride = 1; stride < blockDim.x; stride *= 2){if ((tid % (2 * stride)) == 0){idata[tid] += idata[tid + stride];}//synchronize within block__syncthreads();}//write result for this block to global memif (tid == 0)g_odata[blockIdx.x] = idata[0];}

最后我们看看运行结果,cpu花费89.3ms,而gpu花费2.5ms,运算结果一致。

终端截图

完整代码和编译生成的可执行文件放在这里:

https://github.com/ZihaoZhao/CUDA_study/tree/master/Reduction

总结一下,这一篇我们使用CUDA完成了一个基础的并行归约算法,不过这算法还有很大的优化空间,接下来我们一起对这个算法进行优化,看看能再加快多少。

cuda第一次计算耗时_CUDA编程入门(四)并行归约算法相关推荐

  1. cuda第一次计算耗时_CUDA优化的冷知识10 | GPU卡和Jetson上显存优化的特色

    这一系列文章面向CUDA开发者来解读<CUDA C Best Practices Guide> (CUDA C最佳实践指南) 大家可以访问: 这是一本很经典的手册. CUDA优化的冷知识| ...

  2. cuda编程_CUDA编程入门(四)并行归约算法

    这一篇我们一起学习一下如何使用CUDA实现并行归约算法. 首先我们要知道什么是并行归约.并行归约(Reduction)是一种很基础的并行算法,简单来说,我们有N个输入数据,使用一个符合结合律的二元操作 ...

  3. Linux编程入门四进程

    进程的创建有两种方式:一种是由操纵系统创建,一种是由父进程创建.在系统启动时,操作系统会创建一些进程,它们承担着管理和分配系统资源的任务,这些进程通常被称为系统进程.系统允许一个进程创建新进程(即为子 ...

  4. python复利计算函数_PyThon编程入门:了解python format格式化函数的使用

    在python2之后,新增了这项对字符串.数字可进行格式化的函数.本章为大家介绍关于format函数的使用,章节代码清楚,条理清晰,非常便于理解! 首先给大家准备了利用Format方法写个示例: 下面 ...

  5. halcon编程入门四——halcon缺陷检测

    Region参数 缺陷类型 1.边缘凹凸检测 2.内部凸凹点.瑕疵.污点.内部烫伤.孔洞 3.划痕(用低角度环形光进行打光,将划痕打亮,背景打暗:也可用同轴光,让表面反光不明显) 缺陷图像处理 1.b ...

  6. cuda并行归约算法(不断优化过程)

    归约 方法 #include<cuda_runtime_api.h> #include<cuda_runtime.h> #include<device_launch_pa ...

  7. 脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手

    转自即时通讯网:http://www.52im.net/ 1.引言 网络编程中TCP协议的三次握手和四次挥手的问题,在面试中是最为常见的知识点之一.很多读者都知道"三次"和&quo ...

  8. 题目 1010: [编程入门]利润计算

    题目 1010: [编程入门]利润计算 [编程入门]利润计算 题目 1010: [编程入门]利润计算 题目描述 输入格式 输出格式 样例输入 样例输出 时间限制: 1s 内存限制: 128MB 提交: ...

  9. 脑残式网络编程入门(五):每天都在用的Ping命令,它到底是什么?

    本文引用了公众号纯洁的微笑作者奎哥的技术文章,感谢原作者的分享. 1.前言 老于网络编程熟手来说,在测试和部署网络通信应用(比如IM聊天.实时音视频等)时,如果发现网络连接超时,第一时间想到的就是使用 ...

  10. 脑残式网络编程入门(八):你真的了解127.0.0.1和0.0.0.0的区别?

    本文由"小姐姐养的狗"原创发布于"小姐姐味道"公众号,原题<127.0.0.1和0.0.0.0地址的区别>,收录时有优化和改动.感谢原作者的分享. ...

最新文章

  1. 干货|全面理解无监督学习基础知识
  2. 合并html文件工具,整合 DevTools 和 Chrome
  3. BUUCTF(Pwn) rip
  4. 第二届「星斗奖」获奖名单正式公布
  5. C#内将DataSet分页
  6. iView 实现可编辑表格 1
  7. PHP Mysql-创建数据库
  8. excel找到对应数据的列指标_Excel 行列转换的最简方法
  9. 服务器运行一天死机,服务器死机怎么办?教你排除故障
  10. codeMirror使用记录
  11. 高清录播服务器(服务器是什么)
  12. css3中单位px,em,rem,vh,vw,vmin,vmax的区别及浏览器支持情况
  13. vuetify calendar实现日历考勤
  14. shiro手机无状态登录访问和电脑端登录访问两种方式处理
  15. python源码下载
  16. Ununtu 18.04 安装Carla 0.9.13 以及Carla ros bridge 超级避坑指南(更新于2022.10.20)
  17. 进销存:利用SRM满足客户体验与需求
  18. 智慧城市 低功耗物联网技术与应用并进
  19. 东南大学英语跨考计算机,2018东南大学翻译硕士经验贴(学姐本人)
  20. 2022年中小企业数据安全如何保障?对比华为云与其他云计算大厂

热门文章

  1. 严重BS骗样本的骗子
  2. :《我相信》腾讯QQ vs 360决战版
  3. 2.架构设计的敏捷视图
  4. 第二季-专题6-点亮指路灯
  5. C++中指针和引用区别
  6. 平庸程序员的成长笔记
  7. 用Redis实现分布式锁 与 实现任务队列【转载】
  8. MySQL 5.7: Page Cleaner的刷脏问题
  9. v97fp5后, load问题诊断方法增强.
  10. 无法使用SQL login去登陆SQL Server - 'Password did not match'