机器学习和生物信息学实验室联盟

 找回密码
 注册

QQ登录

只需一步,快速开始

搜索
查看: 1624|回复: 0
打印 上一主题 下一主题

记一次CUDA编程任务

[复制链接]
跳转到指定楼层
楼主
发表于 2017-2-16 10:43:06 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
这个月6号开始,着手解决一个具有实际意义的计算任务,这里简单记录一下,供CUDA初学者参考,大神勿喷。

任务数据有9879896条,每条包含30个整数,任务是计算每两条数据之间的斯皮尔相关系数及其P值。原始数据只有500+MB,因此我并不认为这是个多么大的计算任务。随后稍加计算,我还是很惊呆的,要计算(9879896×9879895)÷2≈4.88亿亿组数据,但此时这还只是个数字概念,我也没意识到时间复杂度和空间复杂度的问题。

1. 计算规模初体验

数据格式:9879896行,30列,每列之间以空格符隔开,例如:
  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. ... ...
  7. ... ...
复制代码

空间复杂度:单纯计算下结果大概有多大吧,每组计算结果包含相关系数和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,不识别虚拟核心。

代码如下:
  1. t1 = clock;
  2. disp('>> loading ...');
  3. A = importdata('D:/MASTER2016/5.CUDA/data-ID-top30-kv3.txt');
  4. b = A'; %由于MATLAB只计算列与列之间的相关系数,因此需要转置操作
  5. disp(etime(clock,t1));

  6. num = size(b, 2);
  7. disp('>> calculating ...');
  8. fid = fopen('D:/MASTER2016/5.CUDA/result-matlab.txt', 'wt');

  9. for i = 1 : num
  10.     for j = i+1 : num
  11.         [m, n] = corr(b(:, i), b(:, j), 'type', 'Spearman', 'tail', 'both');
  12.         if isnan(n) || n>0.05
  13.             continue;
  14.         end
  15.          
  16.         fprintf(fid, 'X%d\tX%d\t%d\t%d\n', i, j, m, n);
  17.     end
  18. end
  19. fclose(fid);
  20. disp('>> OK!');
复制代码

这里我并没有考虑内存空间不够的问题,因为我只是想说明MATLAB的计算速度。开了多颗核心的情况下,MATLAB并没能完全压榨出所有的CPU性能,计算速度缓慢无比,更要命的是,它会越算越慢。据我估算,即使空间复杂度足够,MATLAB也要用超过20年的时间才能算完,这还是不考虑越算越慢的情况。

好了,此方案仅是打酱油。

3. Python多线程

Python语言由于本身的体质问题,Cython下不能调用多核,只能用多线程。理论上是这样,但还是有很多扩展包能够充分压榨出多核CPU性能,例如multiprocessing是其中的佼佼者。multiprocessing用起来也非常简单,考虑到CPU的多核运算下,每颗核心的算力还是很可观的,所有不能把每个计算组都拆成并行线程,那样内存的读写开销反而会使CPU一直在等待状态,不能一直满负载工作。鉴于此,我设计9879895组线程,每组代表某个特定行与剩下的各个数据行形成的数据组。这样每组线程下的运算量还是比较大的,能使CPU尽可能全在满负载状态。

代码如下:
  1. # coding=utf-8

  2. import math
  3. import multiprocessing
  4. import time

  5. import scipy.stats as stats


  6. def calculate2(i, X, all_glb, data_array_glb):
  7.     all = all_glb.value
  8.     result = []
  9.     for j in range(i + 1, all):
  10.         x = X
  11.         y = data_array_glb[j]
  12.         if math.fsum(x) == 0 or math.fsum(y) == 0:
  13.             continue
  14.         corr, p = stats.spearmanr(x, y)
  15.         if p > 0.05:
  16.             continue
  17.         result.append([i + 1, j + 1, corr, p])
  18.     return result


  19. if __name__ == "__main__":

  20.     multiprocessing.freeze_support()

  21.     input_file = 'D:/MASTER2016/5.CUDA/data-ID-top30-kv3.txt'
  22.     output_file = 'D:/MASTER2016/5.CUDA/result-python.txt'

  23.     print '>> loading ...'
  24.     start = time.clock()
  25.     data = open(input_file)
  26.     data_array = []
  27.     for line in data:
  28.         data_array.append(map(int, line.strip().split(' ')))
  29.     data.close()
  30.     print time.clock()-start, 's'

  31.     print '>> calculating ...'
  32.     results = []
  33.     pool_size = 8
  34.     pool = multiprocessing.Pool(processes=pool_size)
  35.     all = len(data_array)
  36.     manager = multiprocessing.Manager()
  37.     all_share = manager.Value('i', int(all))
  38.     data_array_share = manager.list(data_array)
  39.     for i in range(all):
  40.         data_X = data_array[i]
  41.         results.append(pool.apply_async(calculate2, args=(i, data_X, all_share, data_array_share)))
  42.     pool.close()
  43.     pool.join()
  44.     print time.clock() - start, 's'
  45.     data_array = None

  46.     print '>> saving ...'
  47.     data2 = open(output_file, 'w')
  48.     for res in results:
  49.         temp_list = res.get()
  50.         for temp in temp_list:
  51.             data2.write('X'+str(temp[0])+'\t'+'X'+str(temp[1])+'\t'+str(temp[2])+'\t'+str(temp[3])+'\n')
  52.     print time.clock()-start, 's'
  53.     data2.close()
复制代码

这里,我依然没有考虑空间复杂度问题,因为要先看看计算能力是否能满足任务要求。注意需要安装multiprocessing包。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. #define ROWS 15000
  10. #define COLS 30

  11. // 定义每一块内的线程个数,GT720最多是1024(必须大于总矩阵的列数:30)
  12. #define NUM_THREADS 1024


  13. bool InitCUDA()
  14. {
  15.     int count;
  16.     cudaGetDeviceCount(&count);
  17.     if (count == 0) {
  18.         fprintf(stderr, "There is no device.\n");
  19.         return false;
  20.     }
  21.     int i;
  22.     for (i = 0; i < count; i++) {
  23.         cudaDeviceProp prop;
  24.         if (cudaGetDeviceProperties(&prop, i) == cudaSuccess) {
  25.             if (prop.major >= 1) {
  26.                 break;
  27.             }
  28.         }
  29.     }
  30.     if (i == count) {
  31.         fprintf(stderr, "There is no device supporting CUDA 1.x.\n");
  32.         return false;
  33.     }
  34.     cudaSetDevice(i);
  35.     return true;
  36. }

  37. __device__ float meanForRankCUDA(int num)
  38. {
  39.     float sum = 0;
  40.     for (int i = 0; i <= num; i++) {
  41.         sum += i;
  42.     }
  43.     return sum / (num + 1);
  44. }


  45. __device__ float meanForArrayCUDA(float array[], int len)
  46. {
  47.     float sum = 0;
  48.     for (int i = 0; i < len; i++) {
  49.         sum += array[i];
  50.     }
  51.     return sum / len;
  52. }


  53. __device__ float spearmanKernel(int Xarray[], int Yarray[])
  54. {
  55.     //1,对原先的数据进行排序,相同的值取平均值
  56.     float Xrank[30];
  57.     float Yrank[30];
  58.     int col = 30;

  59.     for (int i = 0; i < col; i++) {
  60.         int bigger = 1;
  61.         int equaer = -1;
  62.         for (int j = 0; j < col; j++) {
  63.             if (Xarray[i] < Xarray[j]) {
  64.                 bigger = bigger + 1;
  65.             }
  66.             else if (Xarray[i] == Xarray[j]) {
  67.                 equaer = equaer + 1;
  68.             }
  69.         }
  70.         Xrank[i] = bigger + meanForRankCUDA(equaer);
  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 (Yarray[i] < Yarray[j]) {
  77.                 bigger = bigger + 1;
  78.             }
  79.             else if (Yarray[i] == Yarray[j]) {
  80.                 equaer = equaer + 1;
  81.             }
  82.         }
  83.         Yrank[i] = bigger + meanForRankCUDA(equaer);
  84.     }

  85.     //2,计算斯皮尔曼相关性系数
  86.     float numerator = 0;
  87.     float denominatorLeft = 0;
  88.     float denominatorRight = 0;
  89.     float meanXrank = meanForArrayCUDA(Xrank, col);
  90.     float meanYrank = meanForArrayCUDA(Yrank, col);
  91.     for (int i = 0; i < col; i++) {
  92.         numerator += (Xrank[i] - meanXrank) * (Yrank[i] - meanYrank);
  93.         denominatorLeft += powf(Xrank[i] - meanXrank, 2);
  94.         denominatorRight += powf(Yrank[i] - meanYrank, 2);
  95.     }
  96.     float corr = 0;
  97.     if ((denominatorLeft != 0) && (denominatorRight != 0)) {
  98.         corr = numerator / sqrtf(denominatorLeft * denominatorRight);
  99.     }
  100.     return corr;
  101. }


  102. __global__ static void spearCUDAShared(const int* a, size_t lda, float* c, size_t ldc, float* d, size_t ldd)
  103. {
  104.     extern __shared__ int data[];
  105.     const int tid = threadIdx.x;
  106.     const int row = blockIdx.x;
  107.     int i, j;
  108.     // 同步第1行~倒数第二行到共享内存,行数由block个数(总数据矩阵的行数-1)控制,每个block共享一行数据
  109.     if (tid < 30) {
  110.         data[tid] = a[row * lda + tid];
  111.     }
  112.     __syncthreads();

  113.     int cal_per_block = gridDim.x - row; // 每个块分担的计算量
  114.     int cal_per_thread = cal_per_block / blockDim.x + 1; // 每个线程分担的计算量
  115.     // 分配各线程计算任务,通过for循环控制在一个线程需要计算的组数
  116.     for (i = row + cal_per_thread * tid; i < (row + cal_per_thread * (tid + 1)) && i < gridDim.x; i++) {
  117.         int j_row[30]; // 存放总数据矩阵的第j行
  118.         for (j = 0; j < 30; j++) {
  119.             j_row[j] = a[(i + 1)*lda + j];
  120.         }
  121.         float corr = spearmanKernel(data, j_row);
  122.         c[row * ldc + (i + 1)] = corr;
  123.         float t_test = 0;
  124.         if (corr != 0) t_test = corr*(sqrtf((30 - 2) / (1 - powf(corr, 2))));
  125.         d[row * ldd + (i + 1)] = t_test;
  126.         //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]);
  127.     }
  128. }


  129. clock_t matmultCUDA(const int* a, float* c, float* d)
  130. {
  131.     int *ac;
  132.     float *cc, *dc;
  133.     clock_t start, end;
  134.     start = clock();

  135.     size_t pitch_a, pitch_c, pitch_d;
  136.     // 开辟a、c、d在GPU中的内存
  137.     cudaMallocPitch((void**)&ac, &pitch_a, sizeof(int)* COLS, ROWS);
  138.     cudaMallocPitch((void**)&cc, &pitch_c, sizeof(float)* ROWS, ROWS);
  139.     cudaMallocPitch((void**)&dc, &pitch_d, sizeof(float)* ROWS, ROWS);
  140.     // 复制a从CPU内存到GPU内存
  141.     cudaMemcpy2D(ac, pitch_a, a, sizeof(int)* COLS, sizeof(int)* COLS, ROWS, cudaMemcpyHostToDevice);

  142.     spearCUDAShared << <ROWS - 1, NUM_THREADS, sizeof(int)* COLS >> > (ac, pitch_a / sizeof(int), cc, pitch_c / sizeof(float), dc, pitch_d / sizeof(float));

  143.     cudaMemcpy2D(c, sizeof(float)* ROWS, cc, pitch_c, sizeof(float)* ROWS, ROWS, cudaMemcpyDeviceToHost);
  144.     cudaMemcpy2D(d, sizeof(float)* ROWS, dc, pitch_d, sizeof(float)* ROWS, ROWS, cudaMemcpyDeviceToHost);
  145.     cudaFree(ac);
  146.     cudaFree(cc);

  147.     end = clock();
  148.     return end - start;
  149. }


  150. void print_int_matrix(int* a, int row, int col) {
  151.     for (int i = 0; i < row; i++) {
  152.         for (int j = 0; j < col; j++) {
  153.             printf("%d\t", a[i * col + j]);
  154.         }
  155.         printf("\n");
  156.     }
  157. }


  158. void print_float_matrix(float* c, int row, int col) {
  159.     for (int i = 0; i < row; i++) {
  160.         for (int j = 0; j < col; j++) {
  161.             printf("%f\t", c[i * col + j]);
  162.         }
  163.         printf("\n");
  164.     }
  165. }

  166. void read_ints(int* a) {
  167.     FILE* file = fopen("D:\\MASTER2016\\5.CUDA\\data-ID-top30-kv.txt", "r");
  168.     int i = 0;
  169.     int count = 0;

  170.     fscanf(file, "%d", &i);
  171.     while (!feof(file))
  172.     {
  173.         a[count] = i;
  174.         count++;
  175.         if (count == ROWS*COLS) break;
  176.         fscanf(file, "%d", &i);
  177.     }
  178.     fclose(file);
  179. }


  180. int main()
  181. {
  182.     int *a; // CPU内存中的总数据矩阵,ROWS行,COLS列
  183.     float *c; // CPU内存中的相关系数结果矩阵,ROWS行,ROWS列
  184.     float *d; // CPU内存中的T值结果矩阵,ROWS行,ROWS列
  185.     a = (int*)malloc(sizeof(int)* COLS * ROWS);
  186.     c = (float*)malloc(sizeof(float)* ROWS * ROWS);
  187.     d = (float*)malloc(sizeof(float)* ROWS * ROWS);

  188.     clock_t start = clock();
  189.     printf(">> loading ... rows: %d, cols: %d", ROWS, COLS);
  190.     read_ints(a);
  191.     clock_t end = clock() - start;
  192.     printf("\nTime used: %.2f s\n", (double)(end) / CLOCKS_PER_SEC);

  193.     //print_int_matrix(a, ROWS, COLS);
  194.     //printf("\n");

  195.     printf(">> calculating ... ");
  196.     printf("\n---------------------------------------");
  197.     printf("\ntotal groups: %lld", (long long)ROWS*(ROWS - 1) / 2);
  198.     printf("\ntotal threads: %d (blocks) * 1024 = %d", (ROWS - 1), (ROWS - 1) * 1024);
  199.     printf("\ntotal space complexity: %lld MB", (long long)((ROWS / 1024) * (ROWS / 1024) * 8));
  200.     printf("\n---------------------------------------");
  201.     if (!InitCUDA()) return 0;
  202.     clock_t time = matmultCUDA(a, c, d);
  203.     double sec = (double)(time + end) / CLOCKS_PER_SEC;
  204.     printf("\nTime used: %.2f s\n", sec);

  205.     printf(">> saving ... ");
  206.     FILE *f = fopen("D:\\MASTER2016\\5.CUDA\\result-c-2.txt", "w");
  207.     for (int i = 0; i < ROWS; i++) {
  208.         for (int j = i + 1; j < ROWS; j++) {
  209.             float t_test = d[i * ROWS + j];
  210.             if (t_test >= 2.042) {
  211.                 fprintf(f, "X%d\tX%d\t%f\t%lf\n", i + 1, j + 1, c[i * ROWS + j], t_test);
  212.             }
  213.         }
  214.     }
  215.     fclose(f);
  216.     end = clock() - start;
  217.     printf("OK\nTime used: %.2f s\n", (double)(end) / CLOCKS_PER_SEC);

  218.     //printf(">> 相关系数结果矩阵: \n");
  219.     //print_float_matrix(c, ROWS, ROWS);
  220.     //printf(">> T值结果矩阵: \n");
  221.     //print_float_matrix(d, ROWS, ROWS);

  222.     getchar();
  223.     return 0;
  224. }
复制代码

需要指出的是,上面程序保存为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. #define ROWS 1000
  10. #define COLS 30

  11. // 控制一次计算占用显存的大小:CONTROL_ROWS*ROWS*8(字节)< 显存
  12. #define CONTROL_ROWS 45

  13. // 定义每一块内的线程个数,GT720最多是1024
  14. #define NUM_THREADS 1024


  15. bool InitCUDA()
  16. {
  17.     int count;
  18.     cudaGetDeviceCount(&count);
  19.     if (count == 0) {
  20.         fprintf(stderr, "There is no device.\n");
  21.         return false;
  22.     }
  23.     int i;
  24.     for (i = 0; i < count; i++) {
  25.         cudaDeviceProp prop;
  26.         if (cudaGetDeviceProperties(&prop, i) == cudaSuccess) {
  27.             if (prop.major >= 1) {
  28.                 break;
  29.             }
  30.         }
  31.     }
  32.     if (i == count) {
  33.         fprintf(stderr, "There is no device supporting CUDA 1.x.\n");
  34.         return false;
  35.     }
  36.     cudaSetDevice(i);
  37.     return true;
  38. }

  39. __device__ float meanForRankCUDA(int num)
  40. {
  41.     float sum = 0;
  42.     for (int i = 0; i <= num; i++) {
  43.         sum += i;
  44.     }
  45.     return sum / (num + 1);
  46. }


  47. __device__ float meanForArrayCUDA(float array[], int len)
  48. {
  49.     float sum = 0;
  50.     for (int i = 0; i < len; i++) {
  51.         sum += array[i];
  52.     }
  53.     return sum / len;
  54. }


  55. __device__ float spearmanKernel(int Xarray[], int Yarray[])
  56. {
  57.     //1,对原先的数据进行排序,相同的值取平均值
  58.     float Xrank[30];
  59.     float Yrank[30];
  60.     int col = 30;

  61.     for (int i = 0; i < col; i++) {
  62.         int bigger = 1;
  63.         int equaer = -1;
  64.         for (int j = 0; j < col; j++) {
  65.             if (Xarray[i] < Xarray[j]) {
  66.                 bigger = bigger + 1;
  67.             }
  68.             else if (Xarray[i] == Xarray[j]) {
  69.                 equaer = equaer + 1;
  70.             }
  71.         }
  72.         Xrank[i] = bigger + meanForRankCUDA(equaer);
  73.     }
  74.     for (int i = 0; i < col; i++) {
  75.         int bigger = 1;
  76.         int equaer = -1;
  77.         for (int j = 0; j < col; j++) {
  78.             if (Yarray[i] < Yarray[j]) {
  79.                 bigger = bigger + 1;
  80.             }
  81.             else if (Yarray[i] == Yarray[j]) {
  82.                 equaer = equaer + 1;
  83.             }
  84.         }
  85.         Yrank[i] = bigger + meanForRankCUDA(equaer);
  86.     }

  87.     //2,计算斯皮尔曼相关性系数
  88.     float numerator = 0;
  89.     float denominatorLeft = 0;
  90.     float denominatorRight = 0;
  91.     float meanXrank = meanForArrayCUDA(Xrank, col);
  92.     float meanYrank = meanForArrayCUDA(Yrank, col);
  93.     for (int i = 0; i < col; i++) {
  94.         numerator += (Xrank[i] - meanXrank) * (Yrank[i] - meanYrank);
  95.         denominatorLeft += powf(Xrank[i] - meanXrank, 2);
  96.         denominatorRight += powf(Yrank[i] - meanYrank, 2);
  97.     }
  98.     float corr = 0;
  99.     if ((denominatorLeft != 0) && (denominatorRight != 0)) {
  100.         corr = numerator / sqrtf(denominatorLeft * denominatorRight);
  101.     }
  102.     return corr;
  103. }


  104. __global__ static void spearCUDAShared(const int* a, size_t lda, float* c, size_t ldc, float* d, size_t ldd, int cols, int start)
  105. {
  106.     extern __shared__ int data[];
  107.     const int tid = threadIdx.x;
  108.     const int row = blockIdx.x;

  109.     int i, j;
  110.     // 同步第1行~倒数第二行到共享内存,行数由block个数控制,每个block共享一行数据
  111.     if (tid < 30) {
  112.         data[tid] = a[(start + row) * lda + tid];
  113.     }
  114.     __syncthreads();

  115.     int cal_per_block = cols - (start + row); // 每个块分担的计算量
  116.     int cal_per_thread = cal_per_block / blockDim.x + 1; // 每个线程分担的计算量
  117.     // 分配各线程计算任务,通过for循环控制在一个线程需要计算的组数
  118.     for (i = row + cal_per_thread * tid; i < (row + cal_per_thread * (tid + 1)) && i < cols; i++) {
  119.         int j_row[30]; // 存放总数据矩阵的第j行
  120.         for (j = 0; j < 30; j++) {
  121.             j_row[j] = a[(start + i + 1)*lda + j];
  122.         }
  123.         float corr = spearmanKernel(data, j_row);
  124.         c[row * ldc + (start + i + 1)] = corr;
  125.         float t_test = 0;
  126.         if (corr != 0) t_test = corr*(sqrtf((30 - 2) / (1 - powf(corr, 2))));
  127.         d[row * ldd + (start + i + 1)] = t_test;
  128.         //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]);
  129.     }
  130. }


  131. clock_t matmultCUDA(const int* a, float* c, float* d, int start_index, int control_rows)
  132. {
  133.     int *ac;
  134.     float *cc, *dc;
  135.     clock_t start, end;
  136.     start = clock();

  137.     size_t pitch_a, pitch_c, pitch_d;
  138.     // 开辟a、c、d在GPU中的内存
  139.     cudaMallocPitch((void**)&ac, &pitch_a, sizeof(int)* COLS, ROWS);
  140.     cudaMallocPitch((void**)&cc, &pitch_c, sizeof(float)* ROWS, control_rows);
  141.     cudaMallocPitch((void**)&dc, &pitch_d, sizeof(float)* ROWS, control_rows);
  142.     // 复制a从CPU内存到GPU内存
  143.     cudaMemcpy2D(ac, pitch_a, a, sizeof(int)* COLS, sizeof(int)* COLS, ROWS, cudaMemcpyHostToDevice);

  144.     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);
  145.    
  146.     cudaMemcpy2D(c, sizeof(float)* ROWS, cc, pitch_c, sizeof(float)* ROWS, control_rows, cudaMemcpyDeviceToHost);
  147.     cudaMemcpy2D(d, sizeof(float)* ROWS, dc, pitch_d, sizeof(float)* ROWS, control_rows, cudaMemcpyDeviceToHost);
  148.     cudaFree(ac);
  149.     cudaFree(cc);
  150.     cudaFree(dc);

  151.     end = clock();
  152.     return end - start;
  153. }


  154. void print_int_matrix(int* a, int row, int col) {
  155.     for (int i = 0; i < row; i++) {
  156.         for (int j = 0; j < col; j++) {
  157.             printf("%d\t", a[i * col + j]);
  158.         }
  159.         printf("\n");
  160.     }
  161. }


  162. void print_float_matrix(float* c, int row, int col) {
  163.     for (int i = 0; i < row; i++) {
  164.         for (int j = 0; j < col; j++) {
  165.             printf("%f\t", c[i * col + j]);
  166.         }
  167.         printf("\n");
  168.     }
  169. }

  170. void read_ints(int* a, char *input_file) {
  171.     FILE* file = fopen(input_file, "r");
  172.     int i = 0;
  173.     int count = 0;

  174.     fscanf(file, "%d", &i);
  175.     while (!feof(file))
  176.     {
  177.         a[count] = i;
  178.         count++;
  179.         if (count == ROWS*COLS) break;
  180.         fscanf(file, "%d", &i);
  181.     }
  182.     fclose(file);
  183. }

  184. void clear_ints(char * out_file) {
  185.     FILE *f = fopen(out_file, "w");
  186.     fclose(f);
  187. }

  188. void cal_and_save(int i, int *a, char *out_file, int control_rows) {
  189.     float *c; // CPU内存中的相关系数结果矩阵,ROWS行,ROWS列
  190.     float *d; // CPU内存中的T值结果矩阵,ROWS行,ROWS列
  191.     c = (float*)malloc(sizeof(float)* control_rows * ROWS);
  192.     d = (float*)malloc(sizeof(float)* control_rows * ROWS);

  193.     clock_t time = matmultCUDA(a, c, d, i, control_rows);

  194.     FILE *f = fopen(out_file, "a");
  195.     for (int m = 0; m < control_rows; m++) {
  196.         for (int n = i + m + 1; n < ROWS; n++) {
  197.             float t_test = d[m * ROWS + n];
  198.             if (t_test >= 2.042) {
  199.                 fprintf(f, "X%d\tX%d\t%f\t%lf\n", i + m + 1, n + 1, c[m * ROWS + n], t_test);
  200.             }
  201.         }
  202.     }
  203.     fclose(f);

  204.     //printf(">> 相关系数结果矩阵: \n");
  205.     //print_float_matrix(c, CONTROL_ROWS, ROWS);
  206.     //printf(">> T值结果矩阵: \n");
  207.     //print_float_matrix(d, CONTROL_ROWS, ROWS);

  208.     free(c);
  209.     free(d);
  210. }

  211. int main()
  212. {
  213.     int *a; // CPU内存中的总数据矩阵,ROWS行,COLS列
  214.     a = (int*)malloc(sizeof(int)* COLS * ROWS);

  215.     char *input_file = "D:\\MASTER2016\\5.CUDA\\data-ID-top30-kv.txt";
  216.     char *out_file = "D:\\MASTER2016\\5.CUDA\\result-c.txt";

  217.     clock_t start = clock();
  218.     printf(">> loading ... rows: %d, cols: %d", ROWS, COLS);
  219.     read_ints(a, input_file);
  220.     clear_ints(out_file);
  221.     clock_t end = clock() - start;
  222.     printf("\nTime used: %.2f s\n", (double)(end) / CLOCKS_PER_SEC);

  223.     //print_int_matrix(a, ROWS, COLS);
  224.     //printf("\n");

  225.     printf(">> calculating ... ");
  226.     printf("\n---------------------------------------");
  227.     printf("\ntotal groups: %lld", (long long)ROWS*(ROWS - 1) / 2);
  228.     printf("\ntotal threads: %d (blocks) * 1024 = %d", (ROWS - 1), (ROWS - 1) * 1024);
  229.     printf("\ntotal space complexity: %lld MB", (long long)((CONTROL_ROWS / 1024) * (ROWS / 1024) * 8));
  230.     printf("\n---------------------------------------");

  231.     if (!InitCUDA()) return 0;

  232.     int i;
  233.     for (i = 0; i < ROWS - 1; i += CONTROL_ROWS) {
  234.         printf("\n>> calculating and saving ... id: %d ... ", i);
  235.         cal_and_save(i, a, out_file, CONTROL_ROWS);
  236.         end = clock() - start;
  237.         printf("Time used: %.2f s", (double)(end) / CLOCKS_PER_SEC);
  238.     }

  239.     // 不能整除的非整数部分需要计算
  240.     //i -= CONTROL_ROWS;
  241.     //int control_rows = ROWS - 1 - i;
  242.     //printf("\n%d", control_rows);
  243.     //if (control_rows > 0) {
  244.     //    printf("\n>> calculating and saving ... id: %d ... ", i);
  245.     //    cal_and_save(i, a, out_file, control_rows);
  246.     //    end = clock() - start;
  247.     //    printf("Time used: %.2f s", (double)(end) / CLOCKS_PER_SEC);
  248.     //}

  249.     printf("\nFinished.\n");

  250.     getchar();
  251.     return 0;
  252. }
复制代码

到现在,由于空间复杂度过高而显存不够的问题通过增加时间复杂度的方法基本解决了。当然在显存足够的情况下,还是一次性算完是最快的,实测CUDA提速100+倍,数据量越大提速越明显。原因一是你必须被逼着按照CUDA的并行模型来写程序,二是GPU的架构设计确实更适合超大并行程序的加速。游戏画面渲染就是这样,你可以想成一个block控制一块屏幕的渲染,每块block的每个线程控制几个像素格的渲染,而这些图像渲染完全可以是独立并行的,GPU的设计初衷,即是增加核心数,不玩命升频率,增加显存带宽,使成百上千的核心数的并行计算能力得到充分释放。

但是目前的程序当然也不是完美的,我没有考虑如何隐藏内存与显存之间数据的传输延迟,没有考虑多块GPU如何联动运算。后面我会思考这些。

无疑,并行计算是计算的未来。而异构并行计算,也将是所有架构师必须增加的学习库。


附件:《CUDA专家手册  GPU编程权威指南》、《CUDA 编程指南4.0中文版》
链接:http://pan.baidu.com/s/1qYMyQfU 密码:75ya


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏 转播转播 分享分享
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

机器学习和生物信息学实验室联盟  

GMT+8, 2024-5-19 10:49 , Processed in 0.073040 second(s), 19 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表