借助树状数组的思想实现cuda版前缀和

news2024/12/25 9:25:33

昨天面试快手,面试官出了一个cuda编程题–实现前缀和。当时没有做出来,一直在思考是否有类似于规约树这样的解法,感觉好难……面试结束后搜了一下cuda前缀和的介绍,发现该问题是一个经典的cuda编程问题,NVIDIA很早之前就给出了一个快速的实现。看别人的博客研究了很久也没太明白,索性自己写一个好了。
前缀和每一个位置的计算都依赖于前一个位置,这就导致无法利用规约树求和算法逐层递减问题规模。但是前缀和的每一个位置都是从0开始到当前位置的和,总感觉和规约树求和脱不了关系。昨天午休的时候突然灵光一闪,想到了树状数组(BIT)(本篇不会涉及树状数组的基础知识,不了解该数据结构请自行查询。)。树状数组本来的用意是解决单点修改和区间和查询类的问题,说不定可以用到前缀和的计算过程中。

1.利用BIT计算前缀和

先不考虑如何去构造树状数组,假设我们已经构造好了,那我们要计算每一个位置的前缀和,就可以每一个位置启动一个线程去利用BIT查询前缀和,复杂度是O(logN)。每一个位置启动一个线程对cuda来说再容易不过了,所以如果构造好了BIT再去计算前缀和对cuda来说不是难事,那问题的关键就在于如何构造BIT。我们先给出利用BIT计算前缀和的cuda实现:

__global__ static void calc_sum_using_bit(int *input, int *output, int n) {
    int tid = blockIdx.x * blockDim.x + threadIdx.x;

    if (tid < n) {
        output[tid] = 0;
        int idx = tid + 1; //BIT要求索引位置从1开始
        while (idx > 0) {
            output[tid] += input[idx - 1];
            idx -= (idx & -idx); //idx&-idx就是BIT中的lowbit定义
        }
    }
}

2.构造BIT

我看NVIDIA官方实现的paper中,给出了一个它们代码的计算示例图,感觉几乎和BIT一模一样,但是不知道为啥它们没有采用BIT来实现。理解BIT可以在O(logN)时间内求区间和的关键就在于BIT中的每个位置不再是单个元素的值,而可能是一系列元素的和,具体是几个元素的和与该位置二进制中末尾的0个数相关。举个例子,位置7末尾没有0就表示该位置只有其自身,位置8末尾三个0就表示它是前8个元素的和,位置12末尾2个0就表示从12开始前面4个元素的和。利用BIT进行单点修改时,我们会从要修改的位置利用lowbit逐渐往更大的位置修改相关位置的值。假定BIT数组长度为20,我们要增加位置7的值,我们除了要修改位置7之外,还要依次修改位置8、位置16的值。因为在BIT中位置8是前8个元素的和,位置7的值变了前8个元素的和当然也会变化,同理位置16的值也要做相应修改。
既然我们理解了BIT每个位置的含义,那就按照定义去计算每个位置的值就好了。计算方法就如下图所示,第一轮中,我们先更新所有偶数位置:将其值更新为val[pos-1]+val[pos];在第二轮中,我们再更新所有4倍数的位置,将其更新为val[pos-2]+val[pos];在第三轮中,我们再更新所有8倍数的位置,将其更新为val[pos-4]+val[pos],依此类推。大家可以实地去演算一下,每一轮迭代,要计算的位置的值正好符合BIT的定义。
在这里插入图片描述

2.1 block内的BIT计算

将BIT的构造用cuda实现还会有一个难题,就是cuda需要考虑block之间的同步,这个问题比较难。虽然cuda 9之后引入了cooperative groups,但是我尝试了一下结果总是不对,无奈放弃。我的解决思路就是分两步走:首先考虑block内的BIT计算,然后再考虑block之间的BIT计算。Block内的BIT计算cuda代码如下:

__global__ static void gen_bit_in_one_block(int *input, int n) {
    int tid = blockIdx.x * blockDim.x + threadIdx.x;

    int idx = 2;
    while (idx <= blockDim.x) {
        if (tid < n && ((tid + 1) & (idx - 1)) == 0) {
            input[tid] += input[tid - (idx >> 1)];
        }
        __syncthreads();
        idx <<= 1;
    }
}

上述代码,如果将while循环改成while(idx <= n)那就是完整的BIT计算,但是在cuda中是不正确的,因为没有考虑块间同步,如果能很好地解决块间同步,则可以直接一个kernel完成BIT的构造。idx表示当前的BIT每个位置从该位置开始要计算的前缀和长度。__syncthreads不能少,因为后面位置的计算会依赖于前面位置的结果,所以必须要保证一个block之内的线程同步。

2.2 block之间的BIT计算

Block之间的BIT计算有两种方案。第一种方案是将块间同步通过kernel调用来实现,将kernel包在while循环中完成BIT的构造,代码如下:

__global__ static void gen_bit_one_layer(int *input, int n, int idx) {
    int tid = blockIdx.x * blockDim.x + threadIdx.x;
    if (tid < n && ((tid + 1) & (idx - 1)) == 0) {
        input[tid] += input[tid - (idx >> 1)];
    }
}

但是当元素较多时,kernel调用次数会比较多,从而影响速度。第二种方案是启动一个block完成块间BIT的计算,代码如下:

__global__ static void gen_bit_between_blocks(int *input, int n, long long idx) {
    int tid = threadIdx.x;
    while (idx <= n) {
        for (long long pos = (tid + 1) * idx; pos <= n; pos += idx * blockDim.x) {
            input[pos - 1] += input[pos - 1 - (idx >> 1)];
        }
        __syncthreads();
        idx <<= 1;
    }
}

调用上述几个核函数的完整代码如下:

inline int get_block_size(int size, int block_size) { 
	return (size + block_size - 1) / block_size;
}
void calc_prefix_sum(int *input, int *output, int n) {
	// copy from cpu to gpu .....

    dim3 dimBlock(256);
    dim3 dimGrid(get_block_size(n, 256));

    gen_bit_in_one_block<<<dimGrid, dimBlock>>>(buffer1, n);

    int idx = 512;
#if 0
    // 元素很多时需要迭代很多轮,速度慢于下面的实现
    while (idx < n) {
        gen_bit_one_layer<<<dimGrid, dimBlock>>>(buffer1, n, idx);
        idx <<= 1;
    }
#else
    gen_bit_between_blocks<<<1, dimBlock>>>(buffer1, n, idx);
#endif
    calc_sum_using_bit<<<dimGrid, dimBlock>>>(buffer1, buffer2, n);
    cudaDeviceSynchronize();
   
	// copy from gpu to cpu .....
}

3.性能

从网上搜到一个最近的开源实现《Parallel Prefix Sum (Scan) with CUDA》,此实现仿照nvidia官方paper对前缀和进行了优化,我们与其进行速度对比。我的电脑CPU是8核AMD Ryzen 7 5800X,GPU是NVIDIA GeForce RTX 3070,表中耗时单位是ms。

10010001万10万100万1000万1亿10亿
cpu000.010.070.584.5640406
gen_bit_one_layer0.020.020.030.050.141.1612.5128
gen_bit_between_blocks0.020.020.020.030.100.808.279
开源bcao0.020.020.040.050.130.686.154

当元素个数小于5万时CPU上最快,小于300万时我的代码会最快,大于300万时开源代码最快,但是差距没有拉开很多。不过,从代码量上来说我的实现代码量要比开源实现小很多,理解也会更容易。如果能将BIT的构造都在一个kernel中实现,速度有机会更进一步,等将来熟悉cooperative groups之后再去尝试。当然,前缀和问题本来就是一个O(n)的问题,复杂度很低,所以CPU实现也能比较快得处理大规模数据,GPU并没有加速很多。但是cuda版的前缀和还是有价值的,尤其在一个需要很多GPU操作的项目中可以避免将数据拷回CPU进行处理。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

论文解读,神经网络全梯度表示《Full-Gradient Representation for Neural Network Visualization》

导语 这篇论文介绍了一种新的工具&#xff0c;称为全梯度&#xff0c;用于解释神经网络的响应。这个全梯度的概念将神经网络的响应分解为两个部分&#xff1a;输入灵敏度和每个神经元的灵敏度分量。 输入灵敏度&#xff1a;输入灵敏度指的是对于神经网络输出的影响程度。它反…

node中使用http创建web服务器

1.案例代码 // 1.导入http模块 const http require(http)// 2.创建web服务器实例 const server http.createServer()// 3.为服务器实例绑定request事件&#xff0c;监听客户的请求 server.on(request,function(req,res){console.log(欢迎来到服务器);// req.url是客户端请求…

【Material-UI】Checkbox组件:标签使用详解

文章目录 一、Checkbox 组件与标签概述1. 组件介绍2. 基本用法 二、Checkbox 标签的关键特性1. 标签与复选框的结合2. 必填项3. 禁用状态4. 带有图标的复选框5. 多行标签 三、Checkbox 标签的实际应用场景1. 表单选择项2. 设置选项3. 同意条款 四、注意事项1. 无障碍支持2. 样式…

windows环境编译ffmpeg +visual studio 2022

最近在配置ffmpeg环境&#xff0c;记录一下坑点。 系统环境 visual stdio 2022 安装c桌面开发人员版 大概8g 实际下载2g左右&#xff0c;配置齐全其余不选。 然后环境配置&#xff0c;这里我使用别人的图&#xff0c;路劲都差不多。找到VS即可 PATH配置&#xff1a; 编译 …

Spring 三级缓存解决循环依赖源码分析

什么是循环依赖&#xff1f; ServiceA依赖ServiceB&#xff0c;ServiceB依赖ServiceA。 启动Spring项目时&#xff0c;如果想实例化ServiceA&#xff0c;创建完ServiceA对象后&#xff0c;需要依赖注入ServiceB的对象&#xff0c;而ServiceB实例化时&#xff0c;需要ServiceA&…

大模型场景应用全集:持续更新中

一、应用场景 1.办公场景 智能办公&#xff1a;文案生成&#xff08;协助构建大纲优化表达内容生成&#xff09;、PPT美化&#xff08;自动排版演讲备注生成PPT&#xff09;、数据分析&#xff08;生成公式数据处理表格生成&#xff09;。 智能会议&#xff1a;会议策划&…

C++之 bind 绑定器深入学习:从入门到精通!

简介 本文详细阐述了 C 中关于 bind 绑定器技术的基本概念和常用技巧。 引入动机 在标准算法库中&#xff0c;有一个算法叫 remove_if&#xff0c;其基本使用如下&#xff1a; #include <iostream> #include <string> #include <algorithm> #include &l…

FANUC发那科模块 A03B-0823-C003 I/0 EXT

IO模块接线 在FANUC系统中IO模块的种类比较多&#xff0c;每种IO模块的使用场合也不相同&#xff0c;每种IO模块的接线脚位也有很大区别&#xff0c;对于电气设计人员来说&#xff0c;清楚知道常用IO模块的接线脚位&#xff0c;才能更好的规划地址、设计图纸&#xff0c;对于设…

MySQL多表

表关系 1.一对多 应用场景 班级和学生 部门和员工 建表原则 设置&#xff08;ForeginKey&#xff09;外键连接 一个表的外键即为另外一张表的主键,以此简历两张表的关系 因此需要再学生表中新增一列&#xff0c;命名为 班级表_id&#xff0c;即班级表的主键&#xff0c;又叫…

【力扣】572.另一棵树的子树

题目描述 给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看…

电脑屏幕录制工具分享5款,附上详细电脑录屏教程(2024全新)

日月更迭&#xff0c;转眼间已经来到了2024年的立秋&#xff0c;在这个数字技术快速发展的时代&#xff0c;电脑录屏技术已经成为了一项不可或缺的技能&#xff0c;无论是用于工作汇报、在线教学、游戏直播还是个人娱乐。那么录屏软件哪个好用呢&#xff1f;接下来&#xff0c;…

QT按钮组

目录 按钮组 Push Button&#xff08;按钮&#xff09; Tool Button&#xff08;图片文字&#xff09; Radio Button(单选&#xff09; Check Button(多选) Command Link Button Dialog Button Box(对话按钮&#xff09; 按钮组 Push Button&#xff08;按钮&#xff09…

手机游戏录屏软件哪个好,3款软件搞定游戏录屏

在智能手机普及的今天&#xff0c;越来越多的人喜欢在手机上玩游戏&#xff0c;并希望能够录制游戏过程或者分享游戏技巧。然而&#xff0c;面对市面上众多的手机游戏录屏软件&#xff0c;很多人可能会陷入选择困难。究竟手机游戏录屏软件哪个好&#xff1f;在这篇文章中&#…

数据跨境传输的安全合规风险如何规避?获取免费解决方案白皮书

在全球化的背景下&#xff0c;企业进行有 效的资源整合&#xff0c;学习海外市场的先进技术和管理经验&#xff0c;寻找新的增长点&#xff0c;实现业务的多元化和 可持续发展&#xff0c;不仅有利于开辟新市场&#xff0c;更有助于巩固和增强企业在全球中的地位。在这种前景 下…

如何把项目上传到Gitee(超详细保姆级教程)

目录预览 一、远程仓库1、新建远程仓库1.1 克隆/下载信息介绍 2、新建分支3、配置私人令牌 二、本地仓库1、初始化本地仓库2、创建分支&#xff0c;并切换到该分支3、设置用户名、邮箱3.1 全局3.2 局部 4、设置Remote地址4.1 远程仓库有文件4.2 远程仓库没有文件 5、拉取最新代…

全面掌握Xilinx FPGA开发技术与实战技巧

FPGA以其灵活性、可定制性和并行处理能力&#xff0c;为工程师提供了实现创新解决方案的强大工具。对于初学者来说&#xff0c;学习FPGA开发需要掌握一些基础知识和技能。 学习FPGA必备的基础知识点&#xff1a; 数字逻辑基础&#xff1a;理解基本的数字逻辑概念&#xff0c;…

基于danceTrack相关论文代码列表

文章目录 数据集下载2023Observation-Centric SORT: Rethinking SORT for Robust Multi-Object Tracking 数据集下载 https://github.com/DanceTrack/DanceTrack 2023 Observation-Centric SORT: Rethinking SORT for Robust Multi-Object Tracking code: https://github.c…

微型导轨:光学仪器精准定位的支撑者

微型导轨是指宽度在25mm以下的导轨系统&#xff0c;通常由导轨和滑块组成&#xff0c;具有体积小、重量轻、精度高、噪音低、寿命长等特点。主要用于支撑和定位光学元件&#xff0c;如镜子、透镜、滤光片等。微型导轨通过提供高精度的运动控制&#xff0c;‌有利于提高设备的性…

重磅发布 |《一本书讲透数据资产入表》在全球数据资产大会上发布

2024年8月2日&#xff0c;全球数据资产大会在厦门举行&#xff0c;数据资产管理标杆厂商亿信华辰正式发布全新力作《一本书讲透数据资产入表》&#xff0c;荣获“数据资产十大先锋机构”&#xff0c;并发表主题演讲&#xff0c;展现其在数据资产管理领域的领军风采与创新实力。…

macOS Java多版本管理工具

macOS Java多版本管理工具 可以使用 sdkman&#xff0c;也可以使用jenv 能用 sdkman 就建议使用 sdkman &#xff0c;用不了就使用 jenv # sdkman的安装及使用 蚁景网安学院-一个开放的网络安全交流学习论坛 # jenv 的安装及使用 # 安装JDK8 下载 JDK8 JDK8下载页面&…