交換運算元的單經線並聯約簡
有時,減少必須在非常小的範圍內執行,作為更大的 CUDA 核心的一部分。例如,假設輸入資料恰好有 32 個元素 - warp 中的執行緒數。在這種情況下,可以分配單個扭曲來執行減少。鑑於 warp 在完美同步中執行,與塊級減少相比,可以刪除許多 __syncthreads()
指令。
static const int warpSize = 32;
__device__ int sumCommSingleWarp(volatile int* shArr) {
int idx = threadIdx.x % warpSize; //the lane index in the warp
if (idx<16) shArr[idx] += shArr[idx+16];
if (idx<8) shArr[idx] += shArr[idx+8];
if (idx<4) shArr[idx] += shArr[idx+4];
if (idx<2) shArr[idx] += shArr[idx+2];
if (idx==0) shArr[idx] += shArr[idx+1];
return shArr[0];
}
shArr
最好是共享儲存器中的陣列。warp 中的所有執行緒的值應該相同。如果 sumCommSingleWarp
被多個 warp 呼叫,則 shArr
應該在 warp 之間不同(每個 warp 中相同)。
引數 shArr
被標記為 volatile
,以確保在指示的位置實際執行對陣列的操作。否則,對 shArr[idx]
的重複分配可以優化為對暫存器的分配,只有最終分配是實際儲存到 shArr
。當發生這種情況時,其他執行緒看不到即時分配,從而產生不正確的結果。注意,你可以將普通的非易失性陣列作為 volatile 引數傳遞,與將非 const 作為 const 引數傳遞時相同。
如果在減少之後不關心 shArr[1..31]
的內容,可以進一步簡化程式碼:
static const int warpSize = 32;
__device__ int sumCommSingleWarp(volatile int* shArr) {
int idx = threadIdx.x % warpSize; //the lane index in the warp
if (idx<16) {
shArr[idx] += shArr[idx+16];
shArr[idx] += shArr[idx+8];
shArr[idx] += shArr[idx+4];
shArr[idx] += shArr[idx+2];
shArr[idx] += shArr[idx+1];
}
return shArr[0];
}
在此設定中,我們刪除了許多 if
條件。額外的執行緒執行一些不必要的新增,但我們不再關心它們產生的內容。由於 warp 在 SIMD 模式下執行,我們實際上並沒有通過讓這些執行緒無所事事來節省時間。另一方面,評估條件確實需要相對較長的時間,因為這些條款的主體非常小。如果 shArr[32..47]
填充 0,則可以刪除初始 if
語句。
經線級減少也可用於提高塊級減少:
__global__ void sumCommSingleBlockWithWarps(const int *a, int *out) {
int idx = threadIdx.x;
int sum = 0;
for (int i = idx; i < arraySize; i += blockSize)
sum += a[i];
__shared__ int r[blockSize];
r[idx] = sum;
sumCommSingleWarp(&r[idx & ~(warpSize-1)]);
__syncthreads();
if (idx<warpSize) { //first warp only
r[idx] = idx*warpSize<blockSize ? r[idx*warpSize] : 0;
sumCommSingleWarp(r);
if (idx == 0)
*out = r[0];
}
}
論壇 &r[idx & ~(warpSize-1)]
基本上是 r + warpIdx*32
。這有效地將 r
陣列分成 32 個元素的塊,並且每個塊被分配給單獨的 warp。