gpu例項分析之求和操作

2021-06-27 08:43:22 字數 4179 閱讀 5050

gpu程式設計就是要利用

gpu強大的運算能力去處理簡單的大量的運算。

cpu的晶元中包含大量的邏輯控制單元,這就決定了

cpu適用於處理複雜的邏輯控制的任務。而在

gpu的晶元中則只有很少的控制單元,但卻整合了大量的運算單元,如果只用於進行影象處理的運算,那麼對運算資源的浪費就太大了。

下面通過乙個程式例項來說明我對gpu

程式設計的理解

該程式引用自《高效能程式設計之

cuda>>19

頁,分析內容為自己根據所學知識,寫出的個人理解。

_global_ void sum(float* a, float* b)

_syncthreads();

} if(tid == 0) }

int main()

本程式是用於實現對乙個[32, 128]

的矩陣,求該矩陣每一行的和,最終輸出乙個

32維的向量b,

b中每乙個元素即原來的矩陣每一行

128個元素的和。

程式最開始定義了乙個核心函式,所謂核心函式,就是將要在gpu

上並行執行的函式,

_global_

關鍵字用於描述該函式是由

cpu呼叫,由

gpu執行的函式。函式的引數

a是輸入,引數

b是輸出。

main函式是由

cpu執行的,在該函式中只有一句對核心函式的呼叫。在實際的可執行**中,還應該包含

cpu對於向量

a的初始化以及賦值操作,以及對輸出的

b變數進行處理的**,這裡省略了那些部分,需要說明的是向量

a與向量

b是位於視訊記憶體上面的。

cuda

提供了cpu

對視訊記憶體進行操作的庫函式。第26行的語句是乙個對核心函式的呼叫。sum是核心函式名,<<<>>>運算子中是核心函式的執行引數,用於說明核心函式中的thread數量,以及thread是如何組織的;這是cuda為了實現透明擴充套件而引入的機制,使用者在呼叫核心函式時只需指明乙個邏輯上的thread的組織,而在具體執行時是由gpu硬體動態排程和執行這些thread,grid、block、thread都是邏輯上的概念。在本程式中<<<32,128>>>說明執行該函式的gpu需要使用32個block,每個block中有128個thread來執行該核心函式。這種邏輯組織方式並不是唯一的,但是要便於執行核心函式的每乙個thread根據這種邏輯組織來選擇所要進行操作的資料,每乙個thread執行相同的指令,但是運算元卻是不同的,這就是simt。()圓括號裡的是函式的引數,核心函式執行時需要用到的資料,與普通函式的引數作用一樣。

下面深入到核心函式sum

中,由於核心函式是由若干個

thread

同時執行的,而每個

thread

雖然是執行相同的操作,但是他們操作的物件卻是不同的

(這樣才能達到並行的目的

),所以當某乙個

thread

在執行這個函式時,他需要知道自己是操作哪乙個物件

(即陣列中哪乙個或哪幾個元素

),這就需要內建變數的幫助了。

blockidx

,threadidx

是內建變數,分別表示當前

thread

所在的block

在整個grid

中的「位置」與當前

thread

在其所在的

block

中的「位置」。之所以用「位置」這個詞來描述。是因為

blockidx

,threadidx

是個三維變數。拿

threadidx

來說,如果當前

thread

所在的block

是個三維的

block

,即x,y,z

方向上都有

thread

,那麼threadidx

可以看成是當前

thread

在block

空間中的座標。當然

block

也可以是二維的或者一維的,這取決於我們

cpu在呼叫核心函式時向

<<<>>>

運算子中傳遞的引數。

blockidx

也是同樣的道理,只不過

blockidx

是屬於grid

空間中的,並且

grid

不支援三維。在本程式中

grid

是一維的,內含32個

block

,而block

也是一維的,內含

128個

thread

。所以本程式中的

blockidx.x,threadidx.x

就表示當前

thread

所在block

在grid

中的「編號」與當前

thread

在其所在的

block

中的「編號」。

sum函式中第六行宣告了乙個靜態的共享儲存陣列,用於存放乙個

block

中的thread

所要處理的一行資料,

_shared_

關鍵字說明這個陣列是分配在共享儲存器中的,

gpu允許乙個

block

中的thread

之間進行通訊,這需要共享儲存器的支援,由於乙個

block

要完成一行資料的求和操作,所以

thread

之間的通訊是必然的。第8行的**用於對共享陣列進行賦值操作,前面已經提到,乙個block中的thread負責一行資料,此操作就是將矩陣a中每一行資料分別複製到32個block中的s_data中。第9行的**是乙個柵欄同步操作,其作用是使得所有的thread都執行完了第8行**後,才能繼續向前推進。因為thread之間的執行速度不是完全一致的,有的thread跑在前面,而有的跑在後面。而若乙個thread還沒有完成賦值操作,但另乙個thread就要使用它的資料,這就會引發錯誤,所以thread之間的通訊需要共享儲存器與柵欄同步共同實現。第11行到第18行就是thread進行的計算操作了,也是我們要實現的功能的核心。該**段實現了一種規約求和的演算法。該演算法可簡單描述如下:

第1次迴圈:每個

block

參與計算

threadt0~t63

,每乙個

threadti

實現s_data[i] = s_data[i] + s_data[i + 64]

。可見在第一次迴圈之後

s_data[0~63]

中的資料和就等於原來的

s_data[0~127]

中的資料和。 第2

次迴圈後

s_data[0~31]

中的資料和又等於進入迴圈前的

s_data[0_127]

中的資料和。

第7次迴圈結束後

s_data[0]

中儲存的資料就是進入迴圈前的

s_data[0~127]

中的資料和。

所以,當7

次迴圈執行結束後,乙個

block

就完成了一行資料的求和操作。

需要注意在每一次迴圈執行末尾都有乙個柵欄同步操作,這是為了防止當某乙個或幾個thread第i

次迴圈還未結束,而另乙個或幾個

thread

已經開始了第

i+1次迴圈,造成錯誤的資料結果。

第19~22

行**就是將計算結果放到輸出陣列中該

block

所對應的位置中,拷貝操作只有乙個

thread

執行。

到此處,本**已經分析完了,其中涉及到的知識點有內建變數,共享儲存,柵欄同步,thread

的組織與分布,其中每一知識點都還可以展開出其他內容,本文省略。可以看到在這個程式中,任務的分配是乙個難點,及將

thread

與運算元對映起來,使用者自己可以定義

thread

的組織方式,這是很靈活的,但是不當的組織方式可能會造成效能的不理想(比如將

block

組織為一維或者二維可能有不同的效能),所以,了解

gpu的內部結構是必要的。本程式的核心**是規約演算法,其思想是將運算元的範圍不斷縮小,每一步都將操作範圍減半,直到得到最終結果。這種思想在演算法設計中非常常見,如二分查詢,每一次都將搜尋範圍減半,直到查詢到資料或證明資料不在陣列中。

Python切片操作例項分析

在很多程式語言中,針對字串提供了擷取函程式設計客棧數,其實目的就是對字串程式設計客棧切片。python沒有針對字串的擷取函式,只需要切片操作就可以完成。切片操作符是序列名後跟乙個方括號,方括號中有3個可選的數字,並用冒號分割,數是可選的,而冒號是必須的。切片操作符中的第乙個數表示切片開始的位置,第二...

django框架單錶操作之增刪改例項分析

首先找到操作的首頁面 如下 css boot rel external nofollow 書列表ebjuyamenef add book rel external nofollow class btn btn success 新增新書 書籍管理 書名 操作 刪除 修改 此處的?id可以改成 iid,...

SPSS相關分析(例項操作版)

簡單相關係數 相關分析是對兩個變數間的相關程度進行分析。單相關分析所用的指標稱為單相關係數,也就是pearson相關係數或者相關係數。通常用 表示總體的相關係數,以 r 表示樣本的相關係數。相關係數的定義 簡單相關係數的檢驗 當我們的資料樣本容量小的時候,可信程度就比較差。我們需要對他進行檢驗。也就...