CUDA 編程模型

概述

一、 主機(jī)與設(shè)備

CUDA 編程模型將 CPU 作為主機(jī) (Host) , GPU 作為協(xié)處理器 (co-processor) 或者設(shè)備 (Device). 在一個系統(tǒng)中可以存在一個主機(jī)和多個設(shè)備。 CPU 主要負(fù)責(zé)進(jìn)行邏輯性強(qiáng)的事物處理和串行計(jì)算, GPU 則專注于執(zhí)行高度線程化的并行處理任務(wù)。 CPU GPU 各自擁有相互獨(dú)立的存儲器地址空間:主機(jī)端的內(nèi)存和設(shè)備端的顯存。 CUDA 對內(nèi)存的操作與一般的 C 程序基本相同,但增加了一種新的 pinned memory ;

二、運(yùn)行在 GPU 上的 CUDA 并行計(jì)算函數(shù)稱為 kernel( 內(nèi)核函數(shù) ) 。一個 kernel 函數(shù)并不是一個完整的程序,而是整個 CUDA 程序中的一個可以被并行執(zhí)行的步驟。

三、 CPU 串行代碼完成的工作包括在 kernel 啟動前進(jìn)行數(shù)據(jù)準(zhǔn)備和設(shè)備初始化的工作,以及在 kernel 之間進(jìn)行一些串行計(jì)算。理想情況是, CPU 串行代碼的作用應(yīng)該只是清理上一個內(nèi)核函數(shù),并啟動下一個內(nèi)核函數(shù)。這樣,就可以在設(shè)備上完成盡可能多的工作,減少主機(jī)與設(shè)備之間的數(shù)據(jù)傳輸。

Kernel 函數(shù)的定義與調(diào)用

一、 運(yùn)行在 GPU 上的程序稱為 kernel( 內(nèi)核函數(shù) ) 。

內(nèi)核函數(shù)必須通過 _global_ 函數(shù)類型限定符定義,并且只能在主機(jī)端代碼中調(diào)用。在調(diào)用時,必須聲明內(nèi)核函數(shù)的執(zhí)行參數(shù)。

注意: __global__ 下劃線在Visual studio中的寫法。

使用一種新 <<<…>>> 執(zhí)行配置語法指定執(zhí)行某一指定內(nèi)核調(diào)用的線程數(shù)。必須先為 Kernel 中用到的數(shù)組或變量分配好足夠的空間,再調(diào)用 kernel 函數(shù)

二、 在設(shè)備端運(yùn)行的線程之間是并行執(zhí)行的,每個線程有自己的 blockID threadID 用于與其他線程相區(qū)分。 BlockID threadID 只能在 kernel 中通過內(nèi)建變量訪問。

三、 內(nèi)建變量不需由程序員定義,是由設(shè)備中的專用寄存器提供的。所以,內(nèi)建變量是只讀的,并且只能在 GPU 端得 kernel 函數(shù)中使用。

線程結(jié)構(gòu)

Kernel 是以 block 為單位執(zhí)行的, CUDA 引入 grid 來表示一系列可以被并行執(zhí)行的 block 的集合。各 block 是并行執(zhí)行的, block 之間無法通信,也沒有執(zhí)行順序。

?

block 內(nèi)通信原理

在同一個 block 中的線程通過共享存儲器 (shared memory) 交換數(shù)據(jù),并通過柵欄同步保證線程間能夠正確地共享數(shù)據(jù)。具體來說,可以在 kernel 函數(shù)中需要同步的位置調(diào)用 _syncthreads() 函數(shù)。

為了保證線程塊中的各個線程能夠有效協(xié)作,訪問共享存儲器的延遲必須很小。所以在 GPU 中,共享存儲器與執(zhí)行單元的物理距離必須很小,處于同一個處理核心中。而為了在硬件上用很小的代價就能實(shí)現(xiàn) _syncthreads() 函數(shù),一個 block 中所有線程的數(shù)據(jù)都必須交由同一處理核心進(jìn)行處理。所以,這導(dǎo)致每個線程塊中的線程數(shù)量、共享存儲器大小、寄存器數(shù)量都要受到處理核心硬件資源的限制。目前,每個 block 里最多只能有 512 個線程。

計(jì)算單元

GPU 內(nèi)部, SM 代表流多處理器,即計(jì)算核心。每個 SM 中又包含 8 個標(biāo)量流處理器 SP 以及少量的其他計(jì)算單元。實(shí)際上, SP 只是執(zhí)行單元,并不是完整的處理核心。處理核心必須包含取指、解碼、分發(fā)邏輯和執(zhí)行單元。隸屬同一 SM 8 SP 共用同一套取指和發(fā)射單元,也共用一塊共享存儲器。

一個 block 必須被分配到一個 SM 中,但是一個 SM 中同一時刻可以有多個活動線程塊等待執(zhí)行。這可以更好地利用執(zhí)行單元的資源,當(dāng)一個 block 進(jìn)行同步或者訪問顯存等高延遲操作時,另一個 block 就可以占用 GPU 執(zhí)行資源。

目前,一個內(nèi)核函數(shù)只有一個 grid ,但在支持 DirectX 11 的硬件中,這一限制將會解除。

真正的執(zhí)行單元

在實(shí)際運(yùn)行中, block 會被分割為更小的線程束 (warp) warp 的大小由硬件的計(jì)算能力版本決定。在采用 Tesla 架構(gòu)的 GPU 中,一個線程束由連續(xù)的 32 個線程組成。 Warp 中的線程只與線程 thread? ID 有關(guān)。在每發(fā)射一條 warp 指令, SM 中的 8 SP 將執(zhí)行這條指令 4 遍。

執(zhí)行模型

CUDA 采用了 SIMT( 單指令多線程 ) 執(zhí)行模型。在 SIMT 模型中,如果需要控制單個線程的行為,這會大大降低效率。