一、概述
一個線程網格是由若干線程塊組成的,每個線程塊是二維、三維的,擁有X軸、Y軸、Z軸。此時,每次最多能開啓Y*X*Z*T 個線程。
通常線程塊中線程數量最好是一個線程束大小的整數倍,即32 的整數倍。由於設備是整個線程束爲單位進行調度,如果我們不把線程塊上的線程數目設成32的整數倍,則最後一個線程束中有一部分線程是沒有用的,因此我們必須設置一個限制條件進行限制,防止處理的元素超出X軸方向上所規定的範圍。
在程序中,要儘量避免使用小的線程塊,因爲這樣做無法充分利用硬件。比如,一張圖片分辨率爲1920*1080,我們可以分配 dim3 block_num(1080, 10) ; dim3 thread_num(192, 1);
在當前費米架構的硬件上,一個SM可以處理8個線程塊,所以上述程序從應用層的角度來說一共需要1350個(總共10800個線程塊 / 每個SM 能調度的8個線程塊)SM來完成實現並行。
二、線程與線程塊
CUDA 中線程與線程塊到底有什麼聯繫?CUDA 的設計是用來將數據分解到並行的線程與線程塊中。它允許我們定義一維、二維、三維的索引(Y*X*T)來方便我們在程序中引用一些並行結構。這樣就使得我們程序的結構和內存數據的分佈建立一一映射,處理的數據能被分配到到單獨的SM 中。不論是在GPU上還是在CPU上,讓數據與處理器保存緊密聯繫能使性能得到很大的提升。
不過,在對數組進行佈局的時候,有一點需要我們特別注意,那就是數組的寬度值最好是線程束大小的整數倍。如果不是,填補數組,使它能充滿最後一個線程束。但是這樣做會增加數據集的大小。此外,我們還需要注意對填充單元處理,它和數組中其他單元的處理不同的。
三、X 與Y 方向的線程索引
在一個線程塊上分佈一個二維數組也意味着需要兩個線程索引,這樣我們纔可以用到二維的方式訪問數組:
注意blockDim.x 與 blockDim.y 的使用,這個結構體是由CUDA 運行時庫提供的,分佈表示X軸和Y軸這兩個維度上線程塊的數目。
例如,計算一個32*16 維的數組,假設調度四個線程塊,我們可以有下面的分佈方式:
在實際中長方形的佈局比正方向的佈局優越,這是爲什麼呢?主要有兩個原因:第一,同一個線程塊中的線程可以通過共享內存進行通信,這是線程協作中一種比較快的方式。第二,在同一個線程束的線程存儲訪問合併在一起了,而在當前費米架構的設備中,高速緩存存儲器的大小是128個字節,一次直接訪問連續的128個字節比兩個分佈訪問64個字節要高效得多。在正方形的佈局中,0-15號線程映射在一個線程塊中,它們訪問一塊內存數據,但與這塊內存相連的數據區則是由另一個線程塊訪問的,因此,這兩塊連續的內存數據通過兩次存儲訪問才獲得,而在長方形的佈局中,這只需要一次存儲訪問的操作。
這兩種佈局方式,線程塊和網格配置如下:
線程網格、線程塊及線程的維度如下:
其中:
gridDim.X ----線程網格X維度上線程塊的數量
gridDim.Y ----線程網格Y維度上線程塊的數量
blockDim.x ---一個線程塊X維度上的線程數量
blockDim.y ---一個線程塊Y維度上的線程數量
threadIdx.x ---一個線程塊X維度上線程索引
threadIdx.x ---一個線程塊Y維度上線程索引
通過找出當前的行索引,然後乘以每一行的線程總數,最後加上在X軸方向上的偏移,我們便可以計算出相對於整個線程網格的絕對線程索引。具體點如下:
thread_idx = ((gridDim.x * blockDim.x) * idy) + idx;
其中:
idx = (blockIdx.x * blockDim.x) + threadIdx.x;
idy = (blockIdx.y * blockDim.y) + threadIdx.y;