继续优化基于树状数组的cuda前缀和

在之前的博客《借助树状数组的思想实现cuda版前缀和》中,我们用三个kernel实现了基于树状数组的cuda版前缀和,但是在数据量较大时速度不如传统的reduce-then-scan方法,主要原因在于跨block的reduce阶段没有充分利用所有的cuda核心。在本博客中,我们尝试进一步优化,将三个kernel减少到两个kernel,并在跨block的reduce阶段尝试使用更多核心来提升性能。
基于树状数组的方法源于传统方法在reduce阶段其实就是构造了一个完备的树状数组,所以我们可以将scan阶段改成基于树状数组查询的方式,从而达到简化代码的目的。

1. Reduce阶段

Reduce阶段完成两个工作:1. block内构造树状数组;2.block之间构造树状数组。Block之内构造树状数组如下图所示,首先步幅为1的两个元素相加,然后步幅为2的两个元素相加,之后是步幅为4、8、16…直到步幅为block_size/2的两个元素相加。最终的结果正好和树状数组的定义一模一样。
在这里插入图片描述
由于cuda编程的特殊性,比较难以实现跨block之间的同步,而为了构造完整的树状数组,我们必须要访问不同block之间的元素。在之前的方案中,我们只启动了一个block来实现跨block的树状数组构造,这样就严重限制了该步骤的并行性。事实上,我们还可以继续借助block之内的树状数组构造来完成block之间的树状数组构造。区别就在于block之内构造树状数组的步幅是1,而block之间的构造步幅不再是1,所以我们只需要传递一个步幅就可以将block之内和block之间的树状数组构造统一起来。按照该思路实现的reduce代码如下:

__global__ static void gen_bit_in_one_block(int *input, long long n, long long offset) {int tid = blockIdx.x * blockDim.x + threadIdx.x;long long pos = (tid + 1) * offset - 1;int id = threadIdx.x;__shared__ int reduced_sum[512];reduced_sum[id] = input[pos];__syncthreads();int offset = 2;
#pragma unroll 9while(offset <= 512) {if(((tid + 1) & (offset - 1)) == 0) {reduced_sum[id] += reduced_sum[id - offset / 2];}__syncthreads();offset <<= 1;}input[pos] = reduced_sum[id];}
}

上面的代码和之前的实现基本一致,主要是增加了一个offset变量用来获取正确的初始步幅。此外,为了加速显存访问,我们引入了共享内存,构造树状数组的过程都在共享内存中完成。
上述代码在构造一个warp内的树状数组时,我们还可以使用__shfl_up_sync来加速,但是收益不明显,感兴趣的可以继续尝试优化。

2. Scan阶段

构造好完整的树状数组之后,我们就可以利用其查询每个位置的前缀和了,代码和之前一样:

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

不过经过仔细分析我们就会发现上面的代码实现不是work-efficient的,我们详细解释一下原因。CPU串行实现计算前缀和只需要n次加法。Reduce阶段总共也只会有n个线程参与求和:n/2 + n/4 + n/8 + … = n,所以reduce阶段是work-efficient的。但是scan阶段,参与运算的线程总数为从1到n的二进制中1的个数,而这个数值是n*logn量级的,所以虽然整体复杂度是O(logn)的,但是参与运算的线程数是O(nlogn)的,所以就不属于work-efficient的实现。

3. 完整调用逻辑

我们借助上面两个kernel实现完整的前缀和计算。代码如下:

void calc_prefix_sum(int *input, int *output, int n) {int *buffer1, *buffer2;cudaMalloc(&buffer1, n * sizeof(int));cudaMalloc(&buffer2, n * sizeof(int));cudaMemcpy(buffer1, input, n * sizeof(int), cudaMemcpyHostToDevice);long long offset = 1;long long count = n;dim3 dimBlock(512);do {dim3 dimGrid(get_block_size(count, 512));gen_bit_in_one_block<<<dimGrid, dimBlock>>>(buffer1, count, offset);count /= 512;offset *= 512;} while(offset < n);dim3 dimGrid2(get_block_size(n, 512));calc_sum_using_bit<<<dimGrid, dimBlock2>>>(buffer1, buffer2, n);cudaMemcpy(output, buffer2, n * sizeof(int), cudaMemcpyDeviceToHost);cudaFree(buffer1);cudaFree(buffer2);
}

相比之前的实现,我们需要循环调用gen_bit_in_one_block来构造完整的树状数组。所以,虽然只有两个kernel,但是调用kernel的次数不止两次。

4. 性能对比

我们用RTX4090来验证性能。这次我们引入cub实现,号称最快的前缀和实现,然后再加上传统的reduce-then-scan实现,看看三种不同实现在不同长度下的性能如何,对比如下(单位毫秒):

长度10010001万10万100万1000万1亿10亿
BIT0.190.190.190.190.220.392.1119.46
BCAO0.020.020.030.030.080.312.2319.59
CUB0.030.030.030.040.040.070.878.69

可以看出,基于树状数组的实现在长度较长时可以与BCAO类似,但是两者都远差于CUB的实现。CUB的实现基于论文《Single-pass Parallel Prefix Scan with Decoupled Look-back》实现,只需要一个kernel一次遍历即可完成前缀和计算,但是这篇论文较难读懂暂时不理解详细的原理,待后续研究明白再仿照其进行实现。用树状数组实现的好处是代码较为简洁,速度也还凑合,可以作为面试中的实现来学习。

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

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

相关文章

Qt图片资源导入

右键项目&#xff0c;点击添加新文件 选择Qt -> Qt Resource File 资源文件起名 如&#xff1a;res 生成res.qrc文件 在项目的同级目录下创建文件夹res&#xff0c;并将准备好的资源粘贴进去 右键qrc文件&#xff0c;选中Open in Editor 添加前缀 前缀是各种类型图片的分类&…

嵌入式第四十六天(51单片机(中断,定时器))

一.独立按键设置1.#include "key.h"void init_key(void) {P1 | (0x0F << 4); }int key_pressed(void) {static int ret 0;if((P1 & (1 << 4)) 0){ret 1;}else if((P1 & (1 << 5)) 0){ret 2;}else if((P1 & (1 << 6)) 0){r…

Visual Studio Code2024安装包及安装教程

一、软件下载软件名称&#xff1a;Visual Studio Code 2024安装环境&#xff1a;window10及以上系统下载链接&#xff1a;https://pan.quark.cn/s/d9831b28c69a解压软件Bandizip下载链接&#xff1a;https://pan.quark.cn/s/a54e79b5d553二、软件安装1、下载后&#xff0c;先解…

fps:游戏玩法

能帮到你的话&#xff0c;就给个赞吧 &#x1f618; 文章目录游戏玩法倒计时僵尸潮游戏成功&失败计时玩法&#xff1a;玩家在计时内存活&#xff0c;成功&#xff1b;反之失败Game界面&#xff1a;由关卡调用计时系统计时完成&#xff1a;调用结果界面结果界面玩家死亡&…

如何建立针对 .NET Core web 程序的线程池的长期监控

如何建立针对 .NET Core web 程序的线程池的长期监控 建立针对 .NET Core Web 应用程序线程池的长期监控是一个系统性的工程&#xff0c;它涉及代码集成、指标收集、存储、可视化和告警。 核心思路 线程池监控不是孤立的&#xff0c;它必须与应用程序的整体性能指标&#xff08…

前端开发学习路径

前端开发学习路径前端开发基础技能HTML、CSS和JavaScript是前端开发的三大核心技术。HTML用于构建网页结构&#xff0c;CSS负责样式设计&#xff0c;JavaScript实现交互功能。掌握这三项技术是学习前端开发的基础。现代前端开发通常需要了解ES6语法&#xff0c;包括箭头函数、解…

一款没有任何限制的免费远程手机控制手机的软件简介

这是一款没有任何限制的免费远程手机控制手机的软件支持安卓和苹果1.安装1.1被控制端安装airdroid1.2控制端air mirror2.登录同一个账号3.控制使用打开控制端软件选择要控制的机器直接点“远程控制“连接上后就可以任意操作被控手机了

在word中使用lateX公式的方法

非常好的问题&#xff01;这是一个许多科研人员和学生都渴望实现的功能。但需要明确的是&#xff1a; **Microsoft Word 本身并不具备“自动”将 LaTeX 代码实时转换为渲染后公式的功能。** 它不像 Overleaf 或 VS Code 的 Markdown 插件那样&#xff0c;输入 $Emc^2$ 就立刻变…

23种设计模式——代理模式(Proxy Pattern)详解

✅作者简介&#xff1a;大家好&#xff0c;我是 Meteors., 向往着更加简洁高效的代码写法与编程方式&#xff0c;持续分享Java技术内容。 &#x1f34e;个人主页&#xff1a;Meteors.的博客 &#x1f49e;当前专栏&#xff1a;设计模式 ✨特色专栏&#xff1a;知识分享 &#x…

webpack scope hositing 和tree shaking

Scope Hoisting&#xff08;作用域提升&#xff09; 和 Tree Shaking&#xff08;摇树优化&#xff09; 是现代前端构建中至关重要的概念。它们是构建工具&#xff08;如 Webpack、Rollup、Vite&#xff09;用来优化最终打包产物的核心技术。 核心概念快速理解 Tree Shaking&am…

手写React状态hook

在日常开发中&#xff0c;我们经常用到 React 的状态管理 Hook&#xff1a;useState 和 useReducer。 但你有没有想过&#xff1a;这些 Hook 内部是怎么实现的&#xff1f;为什么调用 setState 之后组件会重新渲染&#xff1f; 今天我们就来从零手写 useState 和 useReducer&am…

力扣hot100:相交链表与反转链表详细思路讲解(160,206)

问题描述核心思路&#xff1a;双指针交替遍历算法思想&#xff1a; 使用两个指针 pa 和 pb 分别从链表A和链表B的头节点出发&#xff0c;同步向后遍历。当任一指针走到链表末尾时&#xff0c;将其重定位到另一链表的头节点继续遍历。若两链表相交&#xff0c;pa 和 pb 最终会在…

跨平台游戏引擎 Axmol-2.8.1 发布

所有使用 axmol-2.8.0 的开发者都应更新至此版本 Axmol 2.8.1 版本是一个以错误修复和功能改进为主的次要 LTS 长期支持版本&#xff0c;发布时间: 2025 年 9 月 5 日 &#x1f64f;感谢所有对 axmol 项目的贡献者&#xff0c;包括财务赞助者&#xff1a;scorewarrior、peter…

通过PXE的方式实现Ubuntu 24.04 自动安装

PXE自动化安装Ubuntu 24.04的配置文件之前都是通过PXE来自动化安装Redhat系列的&#xff0c;例如&#xff1a;Rocky9、Rocky10、CentOS7、银河麒麟 Kylin-V10、Kylin-V11、OpenEuler 24.03等。现在安装Ubuntu系列的跟红帽的不太一样&#xff0c;所以在这里介绍下。创建三个文件…

AOSP Framework开发的一些超方便的快捷命令

在系统源码中发现的一些命令和快捷方式。我们在编译源码之前执行的source build/envsetup.sh,通过cat build/envsetup.sh发现如下命令 - lunch: lunch <product_name>-<build_variant>Selects <product_name> as the product to build, and <build_…

【Protues仿真】基于AT89C52单片机的数码管驱动事例

目录 0案例视频效果展示 1 AT89C52单片机驱动单个数码管 1.1 数码管基础知识 1.1.1外观与引脚 1.1.2 共阴(CC) vs 共阳(CA) 1.1.3段码表(以数字1为例) 1.1.4驱动方式A. 直连IO(最简单,占用IO多)一个段一根线,共阴或共阳公共端固定接GND/VCC。适合单个数码管、…

01-Redis 发展简史与核心定位解析:从诞生到三大产品矩阵

目录引言一、Redis 的起源与发展&#xff1a;从定制工具到开源生态二、Redis 的核心定位&#xff1a;不止是缓存的多面手三、Redis 三大产品矩阵&#xff1a;按需选择的完整解决方案3.1 Redis Open Source&#xff08;社区版&#xff09;&#xff1a;入门与轻量场景首选3.2 Red…

记录jilu~

centos1、安装最小版Linux 安装必要工具yum -install -y epel-releaseyum -install -y net-toolsyum -install -y vim2、修改hostname hostnamectl net-hostname newhostname3、网络配置文件&#xff0c;网关 &#xff0c; 使用ip &#xff0c;dns。。/etc/sysconfig/network-s…

【Linux基础】fdisk命令详解:从入门到精通的磁盘分区管理完全指南

目录 前言 1 fdisk命令概述 1.1 什么是fdisk 1.2 fdisk的应用场景 1.3 fdisk与其他分区工具的比较 2 fdisk命令的安装与基本语法 2.1 在不同Linux发行版中安装fdisk 2.2 fdisk的基本语法 3 fdisk命令参数详解 3.1 主要参数说明 3.2 交互式命令 4 fdisk操作流程详解…

Flowable 工作流引擎

1、核心类 Flowable 引擎通过 ProcessEngine 作为总入口点&#xff0c;提供了多个核心服务接口&#xff0c;每个服务都负责特定的功能领域&#xff1a;服务名称 (Service Name)主要功能 (Main Functionality)关键操作 (Key Operations)RepositoryService管理流程定义和部署&…