高性能计算-GPU并行扫描

news/2025/4/3 0:02:35/文章来源:https://www.cnblogs.com/anluo8/p/18797669

1. 扫描概念

  • 对数组arr[N]扫描就是得到数组prefix[N],每个元素是之前arr元素的求和.
  • 开扫描定义:prefix1[N] = { arr[0], arr[0]+arr[1], ..., arr[0]+arr[1]+arr[N-1] }
  • 闭扫描定义: prefix2[N] = { 0, arr[0], arr[0]+arr[1], ..., arr[0]+arr[1]+arr[N-12}

2. Hillis steele 扫描算法

(1)一种闭扫描算法。基础的Hillis steele 扫描算法只能扫描单块,步骤复杂度为 O(logN),计算复杂度为 O(N*logN).
(2)基础版算法介绍:假设步长为s每次迭代 >=s的线程参与计算,本线程id作为被加数,往前 -s 索引为加数
image
(3)支持任意block倍数长度的数据输入,介绍:

a) 扫描block:按blocksize划分数据,每个block处理一个数据块,块内进行扫描,输出前缀和和块内求和结果辅助数组
b) 递归扫描辅助数组:递归为了将块内求和辅助数组的结果加到块内扫描结果上,需要扫描辅助数组,如果辅助数组大于256那么要考虑辅助数组递归扫描;直到辅助数组被扫描求和数组block为1,扫描结束,反向依次弹栈把求和数组加到对应前缀数组上。
image
(4)优化:共享内存使用双buffer,读写分离,避免读写 bank冲突
(5)代码:

/*
任意长度扫描,单块扫描使用Hillis Steele算法,使用double buffer避免读写bank冲突;默认输入数据长度是blockSize的倍数
*/#include "common.h"
#include <stdio.h>#define BlockSize 512//单块扫描函数, in 输入数据, out 扫描结果, len1 扫描数据长度,sum block求和数组,len2 sum数组大小
__global__ void kernel_BlockScan(int *in,int * out,int len1,int *sum,int len2)
{//取单块数据到共享内存,使用双buffer,读写分离,避免读写bank冲突__shared__ int arrShared[2*BlockSize];int bid = blockIdx.x;   //块idint tid = blockIdx.x * blockDim.x + threadIdx.x;    //全局线程idint tbid = threadIdx.x; //块内线程idint write = 0;  //初始化第0个buff writeint read = 1;   //初始化第1个buff readif(tid<len1){//共享内存read buffer数据初始化arrShared[read*BlockSize + tbid] = in[tid]; __syncthreads();for(int s=1;s<BlockSize;s<<=1){// Hillis Steele算法:索引大于步长的线程参与计算,对应位置上的倍加数+块内前面步长位置的加数if(tbid>=s){//被加数索引int id = read*BlockSize + tbid;arrShared[write*BlockSize + tbid] = arrShared[id] + arrShared[id -s]; }else//否则保留上一轮计算的前缀和arrShared[write*BlockSize + tbid] = arrShared[read*BlockSize + tbid];//每循环一次,读写buffer区域交换__syncthreads();write = 1 - write;read = 1- read;}// 将block计算结果赋值给outout[tid] = arrShared[read*BlockSize + tbid];//将块内求和按块索引号放求和数组if(tbid==(BlockSize-1))sum[bid] = arrShared[read*BlockSize + tbid];}
}//对第一轮扫描结果后处理函数,len 数据总长度
__global__ void handPost(int* first,int len,int *second)
{int tid = blockDim.x * blockIdx.x + threadIdx.x;int bid = blockIdx.x;int tbid = threadIdx.x; //块内线程id//从第二块开始加上前一个块的sum值if(bid>0 && tid<len){//取出被加数__shared__ int temp;if(tbid==0)temp = second[bid-1];__syncthreads();first[tid] += temp;}
}//输入分块扫描分配及后处理函数,输出结果的空间在外面分配
//这里第一二轮扫描都使用该函数递归调用,n输入数据长度
void scan(int *in,int *arrResult,int n)
{int GridSize = (n-1)/BlockSize + 1;printf("gridSize:%d\n",GridSize);int *arrResultFirstSum;CudaSafeCall(cudaMalloc((void**)&arrResultFirstSum,GridSize*sizeof(int)));kernel_BlockScan<<<GridSize,BlockSize,2*BlockSize*sizeof(int)>>>(in,arrResult,n,arrResultFirstSum,GridSize);CudaCheckError();//循环扫描递归终止条件:假如第一次扫描gridsize=4096大于256,组要循环扫描到 gridsize==1,block的和需要被handpost处理累加到后面if(GridSize==1){cudaFree(arrResultFirstSum);return;}//递归扫描:对本轮计算结果和的数组进行扫描int *arrResultSecond;//cudaMalloc 隐式同步CudaSafeCall(cudaMalloc((void**)&arrResultSecond,GridSize*sizeof(int)));scan(arrResultFirstSum,arrResultSecond,GridSize);//空流核函数隐式同步// cudaDeviceSynchronize();//将本轮扫描结果数组的索引+1作为第一次扫描结果按blockIdx.x的索引作为块内扫描结果的加数,得到最终扫描结果arrResulthandPost<<<GridSize,BlockSize,sizeof(int)>>>(arrResult,n,arrResultSecond);CudaCheckError();//cudafree是隐式同步CudaSafeCall(cudaFree(arrResultFirstSum));CudaSafeCall(cudaFree(arrResultSecond));return;
}int main(int argc ,char** argv)
{int N = 1<<20; //数据规模if(argc>1)N = 1 << atoi(argv[1]);//cpu串行计算int *arrHost = (int*)calloc(N,sizeof(int));int *resultHost = (int*)calloc(N,sizeof(int));int *resultFD = (int*)calloc(N,sizeof(int));float cpuTime = 0.0;initialDataInt(arrHost,N);double start = cpuSecond();//闭扫描//初始化第一个元素,避免循环内有判断,cpu分支预测错误resultHost[0] = arrHost[0];for(int i=1;i<N;i++)resultHost[i] = resultHost[i-1] + arrHost[i]; double end = cpuSecond();cpuTime = (end - start)*1000;//gpu计算int *arrD;int *resultD;float gpuTime = 0.0;CudaSafeCall(cudaMalloc((void**)&arrD,N*sizeof(int)));CudaSafeCall(cudaMalloc((void**)&resultD,N*sizeof(int)));cudaEvent_t startD,endD;CudaSafeCall(cudaEventCreate(&startD));CudaSafeCall(cudaEventCreate(&endD));CudaSafeCall(cudaMemcpy(arrD,arrHost,N*sizeof(int),cudaMemcpyHostToDevice));cudaEventRecord(startD);scan(arrD,resultD,N);CudaCheckError();cudaEventRecord(endD);cudaEventSynchronize(endD);CudaSafeCall(cudaMemcpy(resultFD,resultD,N*sizeof(int),cudaMemcpyDeviceToHost));cudaEventElapsedTime(&gpuTime,startD,endD);cudaEventDestroy(startD);cudaEventDestroy(endD);//结果比对checkResultInt(resultHost,resultFD,N);free(arrHost);free(resultHost);free(resultFD);CudaSafeCall(cudaFree(arrD));CudaSafeCall(cudaFree(resultD));printf("数据规模 %d kB,cpu串行耗时 %.3f ms ;gpu 单个block使用Hillis Steele 扫描(double buffer)计算耗时 %.3f ms,加速比为 %.3f .\n",N>>10,cpuTime,gpuTime,cpuTime/gpuTime);return 0;
}

3. Blelloch扫描算法

(1)一种开扫描算法。基础的 Blelloch扫描算法,只能处理单块数据。步骤复杂度为 O(2logN),计算复杂度为 O(2LogN)。
(2)基础版算法介绍:

a) 规约:对块进行规约,每次循更新计算结果的位置的数值,其他数值不变。
image

b) 清零:迭代
c) 散出:迭代

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/909629.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

硬盘损坏盘片划伤还能恢复数据资料吗?专业数据恢复公司怎么修复

这是一块台式机电脑里的希捷4T机械硬盘,型号是ST4000DM004,是北京某单位客户寄过来的,描述说是突然损坏不识别,出现了吱吱的异响声,先在北京当地找了一家数据恢复公司做了开盘维修处理,说是盘片有损伤,难度很大,在客户前后多次支付备件服务费后,也仅仅做出了100G左右的…

B+树是如何进行查询的?

千里之行,始于足下。 —— 老子因为一个数据页中的记录是有限的,且主键值是有序的,所以通过对所有记录进行分组,然后将组号(槽号)存储到页目录,使其起到索引作用,通过二分查找的方法快速检索到记录在哪个分组,来降低检索的时间复杂度。 但是,当我们需要存储大量的记录…

使用RAGFlow和Docker部署本地知识库

随着DeepSeek的火热,大模型对与广大开发者越来越触手可及。本文介绍了使用 DeepSeek R1 模型来构建本地知识库。Ollama 管理本地模型首先要了解大模型的管理工具 Ollama:安装 OllamaOllama 官网   点击下载安装 Ollama 客户端;安装成功后打开,在终端中输入以下命令来检查是…

CherryStudio使用

一、概述 CherryStudio 是一款集多模型对话、知识库管理、AI 绘画、翻译等功能于一体的全能 AI 助手平台。 CherryStudio 高度自定义的设计、强大的扩展能力和友好的用户体验,使其成为专业用户和 AI 爱好者的理想选择。无论是零基础用户还是开发者,都能在 CherryStudio 中找到…

app测试——adb基本命令

ADB常用的指令:查看当前连接设备 : adb devices进入到shell : adb shell查看日志 : adb logcat ctrl+c退出安装apk文件 : adb install xxx.apk 安装安卓版本后缀apkk包测试包路径:E:\dcs\two\app\baiduyuedu_5520.apk卸载APP : adb uninstall +包名案例:adb uninstall…

移动端动态化建设的演进与实践:从技术革新到生态繁荣

移动端动态化建设已从技术优化演变为企业战略的核心组成部分。尤其在业务迭代加速、生态竞争加剧的背景下,小程序容器技术以其高效、灵活、合规的特性,成为动态化演进的重要方向。未来,随着5G、AI等技术的普及,动态化将进一步推动应用开发范式的变革,为智能终端生态的繁荣…

快节奏业务增长,还是得App混合开发

在用户需求瞬息万变、全球化竞争加剧的背景下,混合开发已从“成本妥协方案”进化为“战略效率工具”。通过跨端框架与小程序的生态协同,企业不仅能实现业务的快速迭代与全球扩张,更能在技术降本与用户体验间找到最佳平衡点。正如某金融科技公司CEO所言:“混合开发不是选择题…

Actor移动

移动玩家需要先修改控制器,再把控制器的数据给到组件旋转非玩家:set actor world rotation 面朝玩家:find look at rotation + rinterp to玩家:首先修改控制器 get control rotation -> set world rotation移动玩家起手式:获取玩家控制器 add input vector向前:get co…

操作系统纷纷闭源,技术创新如何“弯道超车”?

在操作系统闭源化与多平台流量分散的双重挑战下,小程序容器技术凭借其轻量化、跨平台和容器化技术优势,成为企业降本增效的核心解决方案。未来,随着AI技术的深度赋能与行业协作的加强,小程序有望进一步打破系统边界,推动全球应用生态向开放、智能、高效的方向演进。近年来…

GSoC谷歌编程之夏2025招募中,Apache DolphinScheduler需要你的提案!

GSoC谷歌编程之夏活动火热进行中!今年,Apache DolphinScheduler项目也依然参与其中,设立了有趣且有挑战性的项目任务,与通用OIDC认证有关。欢迎有才华的你们来挑战! GSoC的起源与目的 GSoC 即 Google Summer of Code(谷歌编程之夏),是谷歌公司发起的一项全球性活动,旨…

D365 邮箱发送,自动追加 CRM:0000001的问题处理

需要改下这个配置,去掉邮件标题 后面带的 CRM:0000001 记得收藏并关注,掌握更多相关知识!!!

4.1 函数

1.1 定义域 A,记作dom f=A; 值域记作:ran f1.2 函数的特点 A到B的函数是从A到B的二元函数的子集,且A中的每一个元素a都必须是f的有序对(a,b)的第一分量,通常把(x,y)∈f,记作f(x)=y,f把x映射成了y *定义域必须在第一分量里都出现 函数个数:nm,不同关系的个数2mn1.3 常…