前言
在併發,多執行緒環境下,同步是一個很重要的環節。同步即是指程式/執行緒之間的執行順序約定。
本文將介紹如何通過共享記憶體機制實現塊內多執行緒之間的同步。
至於塊之間的同步,需要使用到 global memory,代價較為高昂,目前使用的情況也不多,就先不介紹了。
塊內同步函式:__syncthreads ()
執行緒呼叫此函式後,該執行緒所屬塊中的所有執行緒均執行到這個呼叫點後才會繼續往下執行。
程式碼示例
使用同步思想優化之前一篇博文中提到的陣列求和程式。在新的程式中,讓每個塊中的第一個執行緒將塊中所有執行緒的運算結果都加起來,然後再存入到結果陣列中。這樣,結果陣列的長度與塊數相等 (原來是和匯流排程數相等),大大降低了 CPU 端程式求和的工作量以及需要傳遞進/出視訊記憶體的資料 (程式碼下方如果出現紅色波浪線無視之):
1 // 相關 CUDA 庫 2 #include "cuda_runtime.h" 3 #include "cuda.h" 4 #include "device_launch_parameters.h" 5 6 // 此標頭檔案包含 __syncthreads ()函式 7 #include "device_functions.h" 8 9 #include <iostream> 10 #include <cstdlib> 11 12 using namespace std; 13 14 const int N = 100; 15 16 // 塊數 17 const int BLOCK_data = 3; 18 // 各塊中的執行緒數 19 const int THREAD_data = 10; 20 21 // CUDA初始化函式 22 bool InitCUDA() 23 { 24 int deviceCount; 25 26 // 獲取顯示裝置數 27 cudaGetDeviceCount (&deviceCount); 28 29 if (deviceCount == 0) 30 { 31 cout << "找不到裝置" << endl; 32 return EXIT_FAILURE; 33 } 34 35 int i; 36 for (i=0; i<deviceCount; i++) 37 { 38 cudaDeviceProp prop; 39 if (cudaGetDeviceProperties(&prop,i)==cudaSuccess) // 獲取裝置屬性 40 { 41 if (prop.major>=1) //cuda計算能力 42 { 43 break; 44 } 45 } 46 } 47 48 if (i==deviceCount) 49 { 50 cout << "找不到支援 CUDA 計算的裝置" << endl; 51 return EXIT_FAILURE; 52 } 53 54 cudaSetDevice(i); // 選定使用的顯示裝置 55 56 return EXIT_SUCCESS; 57 } 58 59 // 此函式在主機端呼叫,裝置端執行。 60 __global__ 61 static void Sum (int *data,int *result) 62 { 63 // 宣告共享記憶體 (陣列) 64 extern __shared__ int shared[]; 65 // 取得執行緒號 66 const int tid = threadIdx.x; 67 // 獲得塊號 68 const int bid = blockIdx.x; 69 70 shared[tid] = 0; 71 // 有點像網格計算的思路 72 for (int i=bid*THREAD_data+tid; i<N; i+=BLOCK_data*THREAD_data) 73 { 74 shared[tid] += data[i]; 75 } 76 77 // 塊內執行緒同步函式 78 __syncthreads (); 79 80 // 每個塊內索引為 0 的執行緒對其組內所有執行緒的求和結果再次求和 81 if (tid == 0) { 82 for(int i = 1; i < THREAD_data; i++) { 83 shared[0] += shared[i]; 84 } 85 // result 陣列存放各個塊的計算結果 86 result[bid] = shared[0]; 87 } 88 } 89 90 int main () 91 { 92 // 初始化 CUDA 編譯環境 93 if (InitCUDA()) { 94 return EXIT_FAILURE; 95 } 96 cout << "成功建立 CUDA 計算環境" << endl << endl; 97 98 // 建立,初始化,列印測試陣列 99 int *data = new int [N]; 100 cout << "測試矩陣: " << endl; 101 for (int i=0; i<N; i++) 102 { 103 data[i] = rand()%10; 104 cout << data[i] << " "; 105 if ((i+1)%10 == 0) cout << endl; 106 } 107 cout << endl; 108 109 int *gpudata, *result; 110 111 // 在視訊記憶體中為計算物件開闢空間 112 cudaMalloc ((void**)&gpudata, sizeof(int)*N); 113 // 在視訊記憶體中為結果物件開闢空間 114 cudaMalloc ((void**)&result, sizeof(int)*BLOCK_data); 115 116 // 將陣列資料傳輸進視訊記憶體 117 cudaMemcpy (gpudata, data, sizeof(int)*N, cudaMemcpyHostToDevice); 118 // 呼叫 kernel 函式 - 此函式可以根據視訊記憶體地址以及自身的塊號,執行緒號處理資料。 119 Sum<<<BLOCK_data,THREAD_data,THREAD_data*sizeof (int)>>> (gpudata,result); 120 121 // 在記憶體中為計算物件開闢空間 122 int *sumArray = new int[BLOCK_data]; 123 // 從視訊記憶體獲取處理的結果 124 cudaMemcpy (sumArray, result, sizeof(int)*BLOCK_data, cudaMemcpyDeviceToHost); 125 126 // 釋放視訊記憶體 127 cudaFree (gpudata); 128 cudaFree (result); 129 130 // 計算 GPU 每個塊計算出來和的總和 131 int final_sum=0; 132 for (int i=0; i<BLOCK_data; i++) 133 { 134 final_sum += sumArray[i]; 135 } 136 137 cout << "GPU 求和結果為: " << final_sum << endl; 138 139 // 使用 CPU 對矩陣進行求和並將結果對照 140 final_sum = 0; 141 for (int i=0; i<N; i++) 142 { 143 final_sum += data[i]; 144 } 145 cout << "CPU 求和結果為: " << final_sum << endl; 146 147 getchar(); 148 149 return 0; 150 }
執行結果
PS:矩陣元素是隨機生成的
小結
共享記憶體,或者說這個共享陣列是 CUDA 中實現同步最常用的方法。