繼續優化基于樹狀數組的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
英文地址,請注明出處:http://en.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管理流程定義和部署&…