第五篇:CUDA 並行程式中的同步

穆晨發表於2017-01-19

前言

       在併發,多執行緒環境下,同步是一個很重要的環節。同步即是指程式/執行緒之間的執行順序約定。

       本文將介紹如何通過共享記憶體機制實現塊內多執行緒之間的同步。

       至於塊之間的同步,需要使用到 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 中實現同步最常用的方法。

相關文章