記一次CUDA編程任務


  

  這個月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 }
CUDA第一版

 

  需要指出的是,上面程序保存為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第二版

 

  到現在,由於空間復雜度過高而顯存不夠的問題通過增加時間復雜度的方法基本解決了。當然在顯存足夠的情況下,還是一次性算完是最快的,實測CUDA提速100+倍,數據量越大提速越明顯。原因一是你必須被逼着按照CUDA的並行模型來寫程序,二是GPU的架構設計確實更適合超大並行程序的加速。游戲畫面渲染就是這樣,你可以想成一個block控制一塊屏幕的渲染,每塊block的每個線程控制幾個像素格的渲染,而這些圖像渲染完全可以是獨立並行的,GPU的設計初衷,即是增加核心數,不玩命升頻率,增加顯存帶寬,使成百上千的核心數的並行計算能力得到充分釋放。

  但是目前的程序當然也不是完美的,我沒有考慮如何隱藏內存與顯存之間數據的傳輸延遲,沒有考慮多塊GPU如何聯動運算。后面我會思考這些。

  並行計算是計算的未來。異構並行計算,也將是所有架構師必須增加的學習庫。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM