這個月6號開始,着手解決一個具有實際意義的計算任務。任務數據有9879896條,每條包含30個整數,任務是計算每兩條數據之間的斯皮爾相關系數及其P值。原始數據只有500+MB,因此我並不認為這是個多么大的計算任務。隨后稍加計算,我還是很驚呆的,要計算(9879896×9879895)÷2≈4.88億億組數據,但此時這還只是個數字概念,我也沒意識到時間復雜度和空間復雜度的問題。
1. 計算規模初體驗
數據格式:9879896行,30列,每列之間以空格符隔開,例如:
0 2 0 2 0 0 0 0 0 0 0 40 0 0 35 0 0 53 0 44 0 0 0 0 0 0 0 0 0 0 0 0 1 148 0 0 0 0 0 0 0 0 0 0 1133 0 1 0 0 1820 0 0 0 2 0 0 0 1 0 0 0 0 0 33 1 0 0 0 0 0 0 0 0 0 231 0 0 0 0 402 0 0 0 0 0 0 0 0 0 0 0 0 6 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ... ... ... ...
空間復雜度:單純計算下結果大概有多大吧,每組計算結果包含相關系數和P值,若都以float(占4字節)精度存儲,需要占用內存:4.88億億×8B≈400TB,當然,我們不具備這么大內存,因此無論以何種方式計算,都需要一批批地重復將數據載入內存、計算、存入硬盤這個過程,直到運算完成。那么,存入硬盤的結果會有400TB嗎?不然,P值小於或等於0.05的結果才會需要輸出,因此實際上會遠遠小於這個值,具體會小多少,先運行一批數據后才能做出估算。
時間復雜度:計算的組數規模是(n×(n-1))÷2,那么就看程序能跑多快了。我想先看看MATLAB多線程、Python多線程、Spark分布式計算能跑多快,是否能在最快時間內解決問題。
2. MATLAB多線程
MATLAB寫起來最簡單,計算相關系數和P值都不用操心,一行自帶的函數調用就完成。打開MATLAB左下角的並行池,MATLAB將會自動尋找到機子上有的物理核心,並分配與物理核心數相同的worker。比如我的電腦是4核8線程,它只能開4個worker,不識別虛擬核心。
代碼如下:
t1 = clock; disp('>> loading ...'); A = importdata('D:/MASTER2016/5.CUDA/data-ID-top30-kv3.txt'); b = A'; %由於MATLAB只計算列與列之間的相關系數,因此需要轉置操作 disp(etime(clock,t1)); num = size(b, 2); disp('>> calculating ...'); fid = fopen('D:/MASTER2016/5.CUDA/result-matlab.txt', 'wt'); for i = 1 : num for j = i+1 : num [m, n] = corr(b(:, i), b(:, j), 'type', 'Spearman', 'tail', 'both'); if isnan(n) || n>0.05 continue; end fprintf(fid, 'X%d\tX%d\t%d\t%d\n', i, j, m, n); end end fclose(fid); disp('>> OK!');
這里我並沒有考慮內存空間不夠的問題,因為我只是想說明MATLAB的計算速度。開了多顆核心的情況下,MATLAB並沒能完全壓榨出所有的CPU性能,計算速度緩慢無比,更要命的是,它會越算越慢。據我估算,即使空間復雜度足夠,MATLAB也要用超過20年的時間才能算完,這還是不考慮越算越慢的情況。
好了,此方案僅是打醬油。
3. Python多線程
Python語言由於本身的體質問題,Cython下不能調用多核,只能用多線程。理論上是這樣,但還是有很多擴展包能夠充分壓榨出多核CPU性能,例如multiprocessing是其中的佼佼者。multiprocessing用起來也非常簡單,考慮到CPU的多核運算下,每顆核心的算力還是很可觀的,所有不能把每個計算組都拆成並行線程,那樣內存的讀寫開銷反而會使CPU一直在等待狀態,不能一直滿負載工作。鑒於此,我設計9879895組線程,每組代表某個特定行與剩下的各個數據行形成的數據組。這樣每組線程下的運算量還是比較大的,能使CPU盡可能全在滿負載狀態。
代碼如下:
# coding=utf-8 import math import multiprocessing import time import scipy.stats as stats def calculate2(i, X, all_glb, data_array_glb): all = all_glb.value result = [] for j in range(i + 1, all): x = X y = data_array_glb[j] if math.fsum(x) == 0 or math.fsum(y) == 0: continue corr, p = stats.spearmanr(x, y) if p > 0.05: continue result.append([i + 1, j + 1, corr, p]) return result if __name__ == "__main__": multiprocessing.freeze_support() input_file = 'D:/MASTER2016/5.CUDA/data-ID-top30-kv3.txt' output_file = 'D:/MASTER2016/5.CUDA/result-python.txt' print '>> loading ...' start = time.clock() data = open(input_file) data_array = [] for line in data: data_array.append(map(int, line.strip().split(' '))) data.close() print time.clock()-start, 's' print '>> calculating ...' results = [] pool_size = 8 pool = multiprocessing.Pool(processes=pool_size) all = len(data_array) manager = multiprocessing.Manager() all_share = manager.Value('i', int(all)) data_array_share = manager.list(data_array) for i in range(all): data_X = data_array[i] results.append(pool.apply_async(calculate2, args=(i, data_X, all_share, data_array_share))) pool.close() pool.join() print time.clock() - start, 's' data_array = None print '>> saving ...' data2 = open(output_file, 'w') for res in results: temp_list = res.get() for temp in temp_list: data2.write('X'+str(temp[0])+'\t'+'X'+str(temp[1])+'\t'+str(temp[2])+'\t'+str(temp[3])+'\n') print time.clock()-start, 's' data2.close()
這里,我依然沒有考慮空間復雜度問題,因為要先看看計算能力是否能滿足任務要求。Python的這個多線程下,確實能充分榨干CPU性能,風扇呼呼響,要命的是也存在越算越慢的問題。但是,即使CPU一直這么滿負載運算,我粗略估算了下,也得要個14年+才能算完,也不算越算越慢的情況。
所以,此方案是打醬油2號。
4. Spark方案
Spark方案我並沒有寫完,因為寫着寫着就感覺到。。。肯定還是不行,CPU的算力也就那樣了。就算調12台機器一起跑,也不適合用CPU下的線程模型解決問題了。
這種高並行的計算,要想取得最快計算速度,非GPU莫屬。
5. CUDA方案
CUDA方案下,首先必須清晰地設計好線程模型,即:我需要用到幾塊GPU?我需要在每塊GPU上設計多少個block?每個block設計多少個線程?每個線程分配多少運算量?這四個問題基本決定了CUDA程序的性能和復雜度。
CUDA是一種異構並行解決方案,即CPU用於控制,GPU用於主運算的方案。一個GPU有一個grid,每個grid里有大量block,每個block里有大量thread。在運算時,每個thread都是完全獨立並行地運算,每個線程里的運算靠內核函數控制,這也是CUDA編程的核心,目前只能用CUDA C編寫。因此JCUDA和PyCUDA做的只是內存分配這些CPU端控制的事情,還不能代替GPU端的CUDA C代碼。
如上圖,左邊列是Host端,即CPU上執行的控制端,用於分配GPU內存空間,拷貝內存數據到GPU顯存等等操作。右邊列是Device端,即GPU上的並行模型,由grid,block,thread三者構成。不同型號GPU的最大block數和每個block中的最大thread不同,但是可以查詢。在安裝好CUDA Toolkit后,windows用戶可以進入C:\ProgramData\NVIDIA Corporation\CUDA Samples\v8.0\1_Utilities\deviceQuery目錄,打開相應版本的項目,執行運行查詢。
比如我的機器:
基於此,我設計的線程模型是:比如數據是ROWS行,COLS列,那么有((ROWS-1)×ROWS)÷2組計算,每一行都要與從這行開始后面的每一行進行計算。開辟(ROWS-1)個block,編號0~(ROWS-1)對應着數據的行號。所以,對於第一行,行號是0,要與1~(ROWS-1)的每一行進行計算,一共有(ROWS-1)組,這些計算任務分配給第一塊block的1024個線程上計算。依此類推。這樣做並不是最佳的任務分配方案,因為不是公平分配,編號越靠后的block分配的任務越少。但是,這樣做的好處是便於利用共享內存,加速每一個block內的計算。
比如第一行,將數據第一行存入共享內存,那么它在與其他行分別計算的時候,直接從每個block內的共享內存讀取數據,遠遠比從顯存上的全局內存讀取速度快得多。需要注意的是,每塊block內的共享內存的大小也有硬件限制,上面截圖中可以看到,GTX 950M的共享內存是49152B。
Talk is cheap. Show me the code:

1 #include <time.h> 2 #include <math.h> 3 #include <stdio.h> 4 #include <stdlib.h> 5 #include <assert.h> 6 #include "cuda_runtime.h" 7 #include "device_launch_parameters.h" 8 9 // 定義總數據矩陣的行數和列數 10 #define ROWS 15000 11 #define COLS 30 12 13 // 定義每一塊內的線程個數,GT720最多是1024(必須大於總矩陣的列數:30) 14 #define NUM_THREADS 1024 15 16 17 bool InitCUDA() 18 { 19 int count; 20 cudaGetDeviceCount(&count); 21 if (count == 0) { 22 fprintf(stderr, "There is no device.\n"); 23 return false; 24 } 25 int i; 26 for (i = 0; i < count; i++) { 27 cudaDeviceProp prop; 28 if (cudaGetDeviceProperties(&prop, i) == cudaSuccess) { 29 if (prop.major >= 1) { 30 break; 31 } 32 } 33 } 34 if (i == count) { 35 fprintf(stderr, "There is no device supporting CUDA 1.x.\n"); 36 return false; 37 } 38 cudaSetDevice(i); 39 return true; 40 } 41 42 __device__ float meanForRankCUDA(int num) 43 { 44 float sum = 0; 45 for (int i = 0; i <= num; i++) { 46 sum += i; 47 } 48 return sum / (num + 1); 49 } 50 51 52 __device__ float meanForArrayCUDA(float array[], int len) 53 { 54 float sum = 0; 55 for (int i = 0; i < len; i++) { 56 sum += array[i]; 57 } 58 return sum / len; 59 } 60 61 62 __device__ float spearmanKernel(int Xarray[], int Yarray[]) 63 { 64 //1,對原先的數據進行排序,相同的值取平均值 65 float Xrank[30]; 66 float Yrank[30]; 67 int col = 30; 68 69 for (int i = 0; i < col; i++) { 70 int bigger = 1; 71 int equaer = -1; 72 for (int j = 0; j < col; j++) { 73 if (Xarray[i] < Xarray[j]) { 74 bigger = bigger + 1; 75 } 76 else if (Xarray[i] == Xarray[j]) { 77 equaer = equaer + 1; 78 } 79 } 80 Xrank[i] = bigger + meanForRankCUDA(equaer); 81 } 82 for (int i = 0; i < col; i++) { 83 int bigger = 1; 84 int equaer = -1; 85 for (int j = 0; j < col; j++) { 86 if (Yarray[i] < Yarray[j]) { 87 bigger = bigger + 1; 88 } 89 else if (Yarray[i] == Yarray[j]) { 90 equaer = equaer + 1; 91 } 92 } 93 Yrank[i] = bigger + meanForRankCUDA(equaer); 94 } 95 96 //2,計算斯皮爾曼相關性系數 97 float numerator = 0; 98 float denominatorLeft = 0; 99 float denominatorRight = 0; 100 float meanXrank = meanForArrayCUDA(Xrank, col); 101 float meanYrank = meanForArrayCUDA(Yrank, col); 102 for (int i = 0; i < col; i++) { 103 numerator += (Xrank[i] - meanXrank) * (Yrank[i] - meanYrank); 104 denominatorLeft += powf(Xrank[i] - meanXrank, 2); 105 denominatorRight += powf(Yrank[i] - meanYrank, 2); 106 } 107 float corr = 0; 108 if ((denominatorLeft != 0) && (denominatorRight != 0)) { 109 corr = numerator / sqrtf(denominatorLeft * denominatorRight); 110 } 111 return corr; 112 } 113 114 115 __global__ static void spearCUDAShared(const int* a, size_t lda, float* c, size_t ldc, float* d, size_t ldd) 116 { 117 extern __shared__ int data[]; 118 const int tid = threadIdx.x; 119 const int row = blockIdx.x; 120 int i, j; 121 // 同步第1行~倒數第二行到共享內存,行數由block個數(總數據矩陣的行數-1)控制,每個block共享一行數據 122 if (tid < 30) { 123 data[tid] = a[row * lda + tid]; 124 } 125 __syncthreads(); 126 127 int cal_per_block = gridDim.x - row; // 每個塊分擔的計算量 128 int cal_per_thread = cal_per_block / blockDim.x + 1; // 每個線程分擔的計算量 129 // 分配各線程計算任務,通過for循環控制在一個線程需要計算的組數 130 for (i = row + cal_per_thread * tid; i < (row + cal_per_thread * (tid + 1)) && i < gridDim.x; i++) { 131 int j_row[30]; // 存放總數據矩陣的第j行 132 for (j = 0; j < 30; j++) { 133 j_row[j] = a[(i + 1)*lda + j]; 134 } 135 float corr = spearmanKernel(data, j_row); 136 c[row * ldc + (i + 1)] = corr; 137 float t_test = 0; 138 if (corr != 0) t_test = corr*(sqrtf((30 - 2) / (1 - powf(corr, 2)))); 139 d[row * ldd + (i + 1)] = t_test; 140 //printf("block號:%d, 線程號:%d, 計算組:%d-%d, id號:%d, block個數:%d, 每塊線程個數:%d, 該塊總計算量:%d, 該塊中每個線程計算量:%d, corr: %lf, %d, %d, %d - %d, %d, %d\n", row, tid, row, i + 1, (row*blockDim.x + tid), gridDim.x, blockDim.x, cal_per_block, cal_per_thread, corr, data[0], data[1], data[29], j_row[0], j_row[1], j_row[29]); 141 } 142 } 143 144 145 clock_t matmultCUDA(const int* a, float* c, float* d) 146 { 147 int *ac; 148 float *cc, *dc; 149 clock_t start, end; 150 start = clock(); 151 152 size_t pitch_a, pitch_c, pitch_d; 153 // 開辟a、c、d在GPU中的內存 154 cudaMallocPitch((void**)&ac, &pitch_a, sizeof(int)* COLS, ROWS); 155 cudaMallocPitch((void**)&cc, &pitch_c, sizeof(float)* ROWS, ROWS); 156 cudaMallocPitch((void**)&dc, &pitch_d, sizeof(float)* ROWS, ROWS); 157 // 復制a從CPU內存到GPU內存 158 cudaMemcpy2D(ac, pitch_a, a, sizeof(int)* COLS, sizeof(int)* COLS, ROWS, cudaMemcpyHostToDevice); 159 160 spearCUDAShared << <ROWS - 1, NUM_THREADS, sizeof(int)* COLS >> > (ac, pitch_a / sizeof(int), cc, pitch_c / sizeof(float), dc, pitch_d / sizeof(float)); 161 162 cudaMemcpy2D(c, sizeof(float)* ROWS, cc, pitch_c, sizeof(float)* ROWS, ROWS, cudaMemcpyDeviceToHost); 163 cudaMemcpy2D(d, sizeof(float)* ROWS, dc, pitch_d, sizeof(float)* ROWS, ROWS, cudaMemcpyDeviceToHost); 164 cudaFree(ac); 165 cudaFree(cc); 166 167 end = clock(); 168 return end - start; 169 } 170 171 172 void print_int_matrix(int* a, int row, int col) { 173 for (int i = 0; i < row; i++) { 174 for (int j = 0; j < col; j++) { 175 printf("%d\t", a[i * col + j]); 176 } 177 printf("\n"); 178 } 179 } 180 181 182 void print_float_matrix(float* c, int row, int col) { 183 for (int i = 0; i < row; i++) { 184 for (int j = 0; j < col; j++) { 185 printf("%f\t", c[i * col + j]); 186 } 187 printf("\n"); 188 } 189 } 190 191 void read_ints(int* a) { 192 FILE* file = fopen("D:\\MASTER2016\\5.CUDA\\data-ID-top30-kv.txt", "r"); 193 int i = 0; 194 int count = 0; 195 196 fscanf(file, "%d", &i); 197 while (!feof(file)) 198 { 199 a[count] = i; 200 count++; 201 if (count == ROWS*COLS) break; 202 fscanf(file, "%d", &i); 203 } 204 fclose(file); 205 } 206 207 208 int main() 209 { 210 int *a; // CPU內存中的總數據矩陣,ROWS行,COLS列 211 float *c; // CPU內存中的相關系數結果矩陣,ROWS行,ROWS列 212 float *d; // CPU內存中的T值結果矩陣,ROWS行,ROWS列 213 a = (int*)malloc(sizeof(int)* COLS * ROWS); 214 c = (float*)malloc(sizeof(float)* ROWS * ROWS); 215 d = (float*)malloc(sizeof(float)* ROWS * ROWS); 216 217 clock_t start = clock(); 218 printf(">> loading ... rows: %d, cols: %d", ROWS, COLS); 219 read_ints(a); 220 clock_t end = clock() - start; 221 printf("\nTime used: %.2f s\n", (double)(end) / CLOCKS_PER_SEC); 222 223 //print_int_matrix(a, ROWS, COLS); 224 //printf("\n"); 225 226 printf(">> calculating ... "); 227 printf("\n---------------------------------------"); 228 printf("\ntotal groups: %lld", (long long)ROWS*(ROWS - 1) / 2); 229 printf("\ntotal threads: %d (blocks) * 1024 = %d", (ROWS - 1), (ROWS - 1) * 1024); 230 printf("\ntotal space complexity: %lld MB", (long long)((ROWS / 1024) * (ROWS / 1024) * 8)); 231 printf("\n---------------------------------------"); 232 if (!InitCUDA()) return 0; 233 clock_t time = matmultCUDA(a, c, d); 234 double sec = (double)(time + end) / CLOCKS_PER_SEC; 235 printf("\nTime used: %.2f s\n", sec); 236 237 printf(">> saving ... "); 238 FILE *f = fopen("D:\\MASTER2016\\5.CUDA\\result-c-2.txt", "w"); 239 for (int i = 0; i < ROWS; i++) { 240 for (int j = i + 1; j < ROWS; j++) { 241 float t_test = d[i * ROWS + j]; 242 if (t_test >= 2.042) { 243 fprintf(f, "X%d\tX%d\t%f\t%lf\n", i + 1, j + 1, c[i * ROWS + j], t_test); 244 } 245 } 246 } 247 fclose(f); 248 end = clock() - start; 249 printf("OK\nTime used: %.2f s\n", (double)(end) / CLOCKS_PER_SEC); 250 251 //printf(">> 相關系數結果矩陣: \n"); 252 //print_float_matrix(c, ROWS, ROWS); 253 //printf(">> T值結果矩陣: \n"); 254 //print_float_matrix(d, ROWS, ROWS); 255 256 getchar(); 257 return 0; 258 }
需要指出的是,上面程序保存為filename.cu文件,執行nvcc -o filename filename.cu編譯,執行filename即可運行。其中ROWS是從總數據文件中讀取的行數,用於控制數據規模調試程序,如果ROWS大於或等於總數據行數,那么就是讀取整個文件了。
由於空間復雜度太高,也就是最開始提到的,那么下面做些調整,加個控制參數,每次只計算一定的行數,使顯存滿載但不超出即可。相應地,內核函數中的索引號,保存文件的函數都需要做些微調,代碼如下:

1 #include <time.h> 2 #include <math.h> 3 #include <stdio.h> 4 #include <stdlib.h> 5 #include <assert.h> 6 #include "cuda_runtime.h" 7 #include "device_launch_parameters.h" 8 9 // 定義總數據矩陣的行數和列數 10 #define ROWS 1000 11 #define COLS 30 12 13 // 控制一次計算占用顯存的大小:CONTROL_ROWS*ROWS*8(字節)< 顯存 14 #define CONTROL_ROWS 45 15 16 // 定義每一塊內的線程個數,GT720最多是1024 17 #define NUM_THREADS 1024 18 19 20 bool InitCUDA() 21 { 22 int count; 23 cudaGetDeviceCount(&count); 24 if (count == 0) { 25 fprintf(stderr, "There is no device.\n"); 26 return false; 27 } 28 int i; 29 for (i = 0; i < count; i++) { 30 cudaDeviceProp prop; 31 if (cudaGetDeviceProperties(&prop, i) == cudaSuccess) { 32 if (prop.major >= 1) { 33 break; 34 } 35 } 36 } 37 if (i == count) { 38 fprintf(stderr, "There is no device supporting CUDA 1.x.\n"); 39 return false; 40 } 41 cudaSetDevice(i); 42 return true; 43 } 44 45 __device__ float meanForRankCUDA(int num) 46 { 47 float sum = 0; 48 for (int i = 0; i <= num; i++) { 49 sum += i; 50 } 51 return sum / (num + 1); 52 } 53 54 55 __device__ float meanForArrayCUDA(float array[], int len) 56 { 57 float sum = 0; 58 for (int i = 0; i < len; i++) { 59 sum += array[i]; 60 } 61 return sum / len; 62 } 63 64 65 __device__ float spearmanKernel(int Xarray[], int Yarray[]) 66 { 67 //1,對原先的數據進行排序,相同的值取平均值 68 float Xrank[30]; 69 float Yrank[30]; 70 int col = 30; 71 72 for (int i = 0; i < col; i++) { 73 int bigger = 1; 74 int equaer = -1; 75 for (int j = 0; j < col; j++) { 76 if (Xarray[i] < Xarray[j]) { 77 bigger = bigger + 1; 78 } 79 else if (Xarray[i] == Xarray[j]) { 80 equaer = equaer + 1; 81 } 82 } 83 Xrank[i] = bigger + meanForRankCUDA(equaer); 84 } 85 for (int i = 0; i < col; i++) { 86 int bigger = 1; 87 int equaer = -1; 88 for (int j = 0; j < col; j++) { 89 if (Yarray[i] < Yarray[j]) { 90 bigger = bigger + 1; 91 } 92 else if (Yarray[i] == Yarray[j]) { 93 equaer = equaer + 1; 94 } 95 } 96 Yrank[i] = bigger + meanForRankCUDA(equaer); 97 } 98 99 //2,計算斯皮爾曼相關性系數 100 float numerator = 0; 101 float denominatorLeft = 0; 102 float denominatorRight = 0; 103 float meanXrank = meanForArrayCUDA(Xrank, col); 104 float meanYrank = meanForArrayCUDA(Yrank, col); 105 for (int i = 0; i < col; i++) { 106 numerator += (Xrank[i] - meanXrank) * (Yrank[i] - meanYrank); 107 denominatorLeft += powf(Xrank[i] - meanXrank, 2); 108 denominatorRight += powf(Yrank[i] - meanYrank, 2); 109 } 110 float corr = 0; 111 if ((denominatorLeft != 0) && (denominatorRight != 0)) { 112 corr = numerator / sqrtf(denominatorLeft * denominatorRight); 113 } 114 return corr; 115 } 116 117 118 __global__ static void spearCUDAShared(const int* a, size_t lda, float* c, size_t ldc, float* d, size_t ldd, int cols, int start) 119 { 120 extern __shared__ int data[]; 121 const int tid = threadIdx.x; 122 const int row = blockIdx.x; 123 124 int i, j; 125 // 同步第1行~倒數第二行到共享內存,行數由block個數控制,每個block共享一行數據 126 if (tid < 30) { 127 data[tid] = a[(start + row) * lda + tid]; 128 } 129 __syncthreads(); 130 131 int cal_per_block = cols - (start + row); // 每個塊分擔的計算量 132 int cal_per_thread = cal_per_block / blockDim.x + 1; // 每個線程分擔的計算量 133 // 分配各線程計算任務,通過for循環控制在一個線程需要計算的組數 134 for (i = row + cal_per_thread * tid; i < (row + cal_per_thread * (tid + 1)) && i < cols; i++) { 135 int j_row[30]; // 存放總數據矩陣的第j行 136 for (j = 0; j < 30; j++) { 137 j_row[j] = a[(start + i + 1)*lda + j]; 138 } 139 float corr = spearmanKernel(data, j_row); 140 c[row * ldc + (start + i + 1)] = corr; 141 float t_test = 0; 142 if (corr != 0) t_test = corr*(sqrtf((30 - 2) / (1 - powf(corr, 2)))); 143 d[row * ldd + (start + i + 1)] = t_test; 144 //printf("block號:%d, 線程號:%d, 計算組:%d-%d, id號:%d, block個數:%d, 每塊線程個數:%d, 該塊總計算量:%d, 該塊中每個線程計算量:%d, corr: %lf, %d, %d, %d - %d, %d, %d\n", row, tid, row, i + 1, (row*blockDim.x + tid), gridDim.x, blockDim.x, cal_per_block, cal_per_thread, corr, data[0], data[1], data[29], j_row[0], j_row[1], j_row[29]); 145 } 146 } 147 148 149 clock_t matmultCUDA(const int* a, float* c, float* d, int start_index, int control_rows) 150 { 151 int *ac; 152 float *cc, *dc; 153 clock_t start, end; 154 start = clock(); 155 156 size_t pitch_a, pitch_c, pitch_d; 157 // 開辟a、c、d在GPU中的內存 158 cudaMallocPitch((void**)&ac, &pitch_a, sizeof(int)* COLS, ROWS); 159 cudaMallocPitch((void**)&cc, &pitch_c, sizeof(float)* ROWS, control_rows); 160 cudaMallocPitch((void**)&dc, &pitch_d, sizeof(float)* ROWS, control_rows); 161 // 復制a從CPU內存到GPU內存 162 cudaMemcpy2D(ac, pitch_a, a, sizeof(int)* COLS, sizeof(int)* COLS, ROWS, cudaMemcpyHostToDevice); 163 164 spearCUDAShared << <control_rows, NUM_THREADS, sizeof(int)* COLS >> > (ac, pitch_a / sizeof(int), cc, pitch_c / sizeof(float), dc, pitch_d / sizeof(float), ROWS - 1, start_index); 165 166 cudaMemcpy2D(c, sizeof(float)* ROWS, cc, pitch_c, sizeof(float)* ROWS, control_rows, cudaMemcpyDeviceToHost); 167 cudaMemcpy2D(d, sizeof(float)* ROWS, dc, pitch_d, sizeof(float)* ROWS, control_rows, cudaMemcpyDeviceToHost); 168 cudaFree(ac); 169 cudaFree(cc); 170 cudaFree(dc); 171 172 end = clock(); 173 return end - start; 174 } 175 176 177 void print_int_matrix(int* a, int row, int col) { 178 for (int i = 0; i < row; i++) { 179 for (int j = 0; j < col; j++) { 180 printf("%d\t", a[i * col + j]); 181 } 182 printf("\n"); 183 } 184 } 185 186 187 void print_float_matrix(float* c, int row, int col) { 188 for (int i = 0; i < row; i++) { 189 for (int j = 0; j < col; j++) { 190 printf("%f\t", c[i * col + j]); 191 } 192 printf("\n"); 193 } 194 } 195 196 void read_ints(int* a, char *input_file) { 197 FILE* file = fopen(input_file, "r"); 198 int i = 0; 199 int count = 0; 200 201 fscanf(file, "%d", &i); 202 while (!feof(file)) 203 { 204 a[count] = i; 205 count++; 206 if (count == ROWS*COLS) break; 207 fscanf(file, "%d", &i); 208 } 209 fclose(file); 210 } 211 212 void clear_ints(char * out_file) { 213 FILE *f = fopen(out_file, "w"); 214 fclose(f); 215 } 216 217 void cal_and_save(int i, int *a, char *out_file, int control_rows) { 218 float *c; // CPU內存中的相關系數結果矩陣,ROWS行,ROWS列 219 float *d; // CPU內存中的T值結果矩陣,ROWS行,ROWS列 220 c = (float*)malloc(sizeof(float)* control_rows * ROWS); 221 d = (float*)malloc(sizeof(float)* control_rows * ROWS); 222 223 clock_t time = matmultCUDA(a, c, d, i, control_rows); 224 225 FILE *f = fopen(out_file, "a"); 226 for (int m = 0; m < control_rows; m++) { 227 for (int n = i + m + 1; n < ROWS; n++) { 228 float t_test = d[m * ROWS + n]; 229 if (t_test >= 2.042) { 230 fprintf(f, "X%d\tX%d\t%f\t%lf\n", i + m + 1, n + 1, c[m * ROWS + n], t_test); 231 } 232 } 233 } 234 fclose(f); 235 236 //printf(">> 相關系數結果矩陣: \n"); 237 //print_float_matrix(c, CONTROL_ROWS, ROWS); 238 //printf(">> T值結果矩陣: \n"); 239 //print_float_matrix(d, CONTROL_ROWS, ROWS); 240 241 free(c); 242 free(d); 243 } 244 245 int main() 246 { 247 int *a; // CPU內存中的總數據矩陣,ROWS行,COLS列 248 a = (int*)malloc(sizeof(int)* COLS * ROWS); 249 250 char *input_file = "D:\\MASTER2016\\5.CUDA\\data-ID-top30-kv.txt"; 251 char *out_file = "D:\\MASTER2016\\5.CUDA\\result-c.txt"; 252 253 clock_t start = clock(); 254 printf(">> loading ... rows: %d, cols: %d", ROWS, COLS); 255 read_ints(a, input_file); 256 clear_ints(out_file); 257 clock_t end = clock() - start; 258 printf("\nTime used: %.2f s\n", (double)(end) / CLOCKS_PER_SEC); 259 260 //print_int_matrix(a, ROWS, COLS); 261 //printf("\n"); 262 263 printf(">> calculating ... "); 264 printf("\n---------------------------------------"); 265 printf("\ntotal groups: %lld", (long long)ROWS*(ROWS - 1) / 2); 266 printf("\ntotal threads: %d (blocks) * 1024 = %d", (ROWS - 1), (ROWS - 1) * 1024); 267 printf("\ntotal space complexity: %lld MB", (long long)((CONTROL_ROWS / 1024) * (ROWS / 1024) * 8)); 268 printf("\n---------------------------------------"); 269 270 if (!InitCUDA()) return 0; 271 272 int i; 273 for (i = 0; i < ROWS - 1; i += CONTROL_ROWS) { 274 printf("\n>> calculating and saving ... id: %d ... ", i); 275 cal_and_save(i, a, out_file, CONTROL_ROWS); 276 end = clock() - start; 277 printf("Time used: %.2f s", (double)(end) / CLOCKS_PER_SEC); 278 } 279 280 // 不能整除的非整數部分需要計算 281 //i -= CONTROL_ROWS; 282 //int control_rows = ROWS - 1 - i; 283 //printf("\n%d", control_rows); 284 //if (control_rows > 0) { 285 // printf("\n>> calculating and saving ... id: %d ... ", i); 286 // cal_and_save(i, a, out_file, control_rows); 287 // end = clock() - start; 288 // printf("Time used: %.2f s", (double)(end) / CLOCKS_PER_SEC); 289 //} 290 291 printf("\nFinished.\n"); 292 293 getchar(); 294 return 0; 295 }
到現在,由於空間復雜度過高而顯存不夠的問題通過增加時間復雜度的方法基本解決了。當然在顯存足夠的情況下,還是一次性算完是最快的,實測CUDA提速100+倍,數據量越大提速越明顯。原因一是你必須被逼着按照CUDA的並行模型來寫程序,二是GPU的架構設計確實更適合超大並行程序的加速。游戲畫面渲染就是這樣,你可以想成一個block控制一塊屏幕的渲染,每塊block的每個線程控制幾個像素格的渲染,而這些圖像渲染完全可以是獨立並行的,GPU的設計初衷,即是增加核心數,不玩命升頻率,增加顯存帶寬,使成百上千的核心數的並行計算能力得到充分釋放。
但是目前的程序當然也不是完美的,我沒有考慮如何隱藏內存與顯存之間數據的傳輸延遲,沒有考慮多塊GPU如何聯動運算。后面我會思考這些。
並行計算是計算的未來。異構並行計算,也將是所有架構師必須增加的學習庫。