http://peghoty.blog.163.com/blog/static/493464092013016113254852/
http://blog.csdn.net/augusdi/article/details/12833235
CUDA存储器模型:http://blog.csdn.net/endlch/article/details/44538801
CUDA限定符:http://blog.csdn.net/shouhuxianjian/article/details/42427285
思想即是将内存数据拷贝到显存,在显存上执行并行运算,将结果数据从显存拷贝回内存。
CUDA内有thrust库,类似于C++ stl库。
===========以下是原文=========
挖坑待填。
以上是本机CUDA参数。
需要了解的概念:线程束(wrap),共享内存,常量内存,纹理内存(?,图形学相关,略),流,原子操作。
隶属于同一个SM的8个SP共用同一套取指与发射单元,共用一块共享存储器。
kernel以block为单位执行。
一个block必须被分配到同一块SM中,block的每个thread会发送到一个SP上执行,但一个SM中同一时刻可以有多个活动线程块在等待执行。
同一个block的thread开始于相同的指令地址,理论上能够按不同的分支执行,但实际上由于8个SP共用一套取值与发射单元,同一warp的线程执行的指令是相同的。
如果一个warp的线程跳转如分支语句的同一分支,那么实际执行时间就是这个分支执行时间;
否则,SM需要把每一个分支的指令发射到每个SP,执行时间是执行多个分支的所用时间之和。
故CUDA程序尽量避免分支,尽量warp内不分支。
线程束(warp):一个线程束由32个连续的线程组成。(简单地说,warp包含32个线程是因为每发射一条warp指令,SM中的8个SP就会将这条指令执行4遍)。warp才是真正的执行单位。
杂
原子操作。同时对global内存写操作,可分批进行,改成先线程块对shared内存写操作,结束后shared内存写入global内存。
__syncthreads()实现了线程块内的线程同步,当任意线程运行到BAR标记处后,暂停运行,直到整个block中所有的thread都运行到BAR标记处后才继续执行。
__syncthreads()勿置于分支语句内。
流:名义上多个流,实际上可能就是kenel(GPU运算)和copy(CPU与GPU间数据传送)两个流。每个流都是一个队列,事件被push入队列中等待执行。
for循环的任务切分的时候,有两种方式划分任务。
1.划分成k段,每段由某个线程执行。
2.按模k同余进行划分,for循环每次的递增量为块大小。
一般第2种方式更优,因为是并行执行,故第二种方式保证每次执行的的时候,不同线程访问的数据位置相邻。
并行算法
归约运算: 每次折半。以求和为例,第一次前1/2 + 后1/2;第二次 前1/4 + 后1/4 .。。
int i = blockDim.x/2; while(i != 0) { if (cacheIndex < i) cache[cacheIndex] += cache[cacheIndex + i]; __syncthreads(); i /= 2; } if (cacheIndex == 0) c[blockIdx.x] = cache[0];
更好的优化算法:循环展开等
前缀和运算(Scan):
for(d = 1; (1 << d) < n; d++) for all k in parallel if( k >= (1 << d) ) x[out][k] = x[in][k – (1 << (d-1))] + x[in][k] else x[out][k] = x[in][k]
或
for(d = 1; (1 << d) < n; d++) for all k in parallel tmp = x[k] if( k >= (1 << d) ) tmp = x[k – (1 << (d-1))] + x[k] __syncthreads();//同步 x[k] = tmp
以上两算法运行所需空间至少是原空间的两倍,思想为倍增思想。
还有更高效的Scan算法。
for d:=0 to log2(n-1) do for k from 0 to n-1 by 2^(d+1) in parallel do x[k+2^(d+1)-1]:=x[k+2^(d+1)-1] + x[k+2^(d+1)-1] x[n-1]:=0 for d:=log2(n-1) downto 0 do for k from 0 to n-1 by 2^(d+1) in parallel do t:=x[k+2^d-1] x[k+2^d-1]:=x[k+2^(d+1)-1] x[k+2^(d+1)-1]:=t+x[k+2^(d+1)-1]
书上还有更高效的scan_best_kernel算法,略。
排序算法:
基于比较的排序:排序网络
基于非比较的排序:并行基数排序。前置技能:Scan。
并行基数排序算法: 按二进制位考虑。 以00101101为例。排完序后应当是12473568。 二进制翻转: 11010010 统计前缀和: 12233344 如果当前位是0,则写入对应位置。 第1个数写入首位置,第2个数写入第二个位置,第4个数写入第三个位置,第7个数写入第四个位置。 再对当前位是1的进行写入,位置下标 + 4(0的个数)。
矩阵乘法优化:
矩阵运算A*B = C, A为m*p的矩阵,B为p*n的矩阵。
优化1:
将C分块,每个线程块处理C中的某一块,例如d*d的一小块。
那么每个线程块需要完成d*p的矩阵与p*d的矩阵相乘的运算。
为了高效访存,每个线程块再对d*p和p*d的矩阵的p进行划分,看成多个矩阵块相乘后累加。
每个小块为d*q和q*d的大小,开在shared memory内,节约了大量global memory带宽。
(虽然循环次数会增加,但访存效率得到了高效提升)
优化2:
利用寄存器资源优化,效率更高,但略为繁琐。
矩阵转置优化:
无优化:拷贝至GPU内存,置换后拷贝回CPU内存。缺点:输入时每个block按行读入,满足合并访问条件;输出时数据间隔过大,不满足合并访问条件。
优化1:
分块,每个块是一个小方阵矩阵,如16*16。
输入时,每个线程块操作一个16*16方阵,通过shared memory完成16*16小方阵转置。
之后将大矩阵按块转置输出至global memory,每个线程块内无需再转置,满足合并访问条件。
shared memory数组大小设置成16*17而不是16*16,这样每行中处于同一列的数据就会被存储在不同的shared memory bank中,避免了bank conflict。
优化2:
上述无优化与优化1均存在分区冲突问题。优化2算法进行了for循环操作,暂未深入研究。
CUDA程序优化
grid和block的维度设计
grid的尺寸大一点较好。
为了有效利用执行单元,每个block的线程数应当是32的整数倍,最好让线程数量保持在64 ~ 256之间。
block维度和每个维度上的尺寸的主要作用是避免做整数除法和求模运算。实际中视具体情况而定。
如果问题规模对划分方式不敏感,应该让blockDim.x为16或16的整数倍,提高访问global memory和shared memory的效率。
存储器访问优化
灵活运用各种存储器的特性,实现最大可用带宽。
指令流优化
等
CUDA作业
作业1 简单CUDA应用,矩阵乘法
1 #include <bits/stdc++.h> 2 //#include "cuda_runtime.h" 3 //#include "device_launch_parameters.h" 4 5 using namespace std; 6 #define N 2000 7 8 const int block = 1<<12; 9 const int thread = 1<<10; 10 11 long long a[N][N]; 12 long long b[N][N]; 13 long long c[N][N]; 14 void init() { 15 for(int i = 0; i < N; i++) 16 for(int j = 0; j < N; j++) 17 a[i][j] = i*i+j, b[i][j] = i+j*j, c[i][j] = 0; 18 } 19 20 __global__ void init_cuda(long long *c) { 21 int id = blockIdx.x*blockDim.x+threadIdx.x; 22 if(id < N*N) c[id] = 0; 23 } 24 25 __global__ void mul_cuda(long long *a, long long *b, long long *c) { 26 int id = blockIdx.x*blockDim.x+threadIdx.x; 27 if(id < N*N) { 28 int row = id/N, col = id-row*N; 29 for(int k = 0; k < N; k++) 30 c[id] += a[row*N+k]*b[k*N+col]; 31 } 32 } 33 34 35 36 int main(int argc, char** argv) { 37 int cstart = clock(); 38 init(); 39 if(argv[1][0] == '0') { 40 puts("not cuda"); 41 for(int i = 0; i < N; i++) 42 for(int j = 0; j < N; j++) 43 for(int k = 0; k < N; k++) 44 c[i][k] += a[i][j]*b[j][k]; 45 } 46 else { 47 puts("cuda"); 48 long long *dev_a, *dev_b, *dev_c; 49 cudaMalloc( (void**)&dev_a, sizeof a ); 50 cudaMemcpy(dev_a, a, sizeof a, cudaMemcpyHostToDevice); 51 52 cudaMalloc( (void**)&dev_b, sizeof b ); 53 cudaMemcpy(dev_b, b, sizeof b, cudaMemcpyHostToDevice); 54 55 cudaMalloc( (void**)&dev_c, sizeof c ); 56 57 58 init_cuda<<<block, thread>>>(dev_c); 59 mul_cuda<<<block, thread>>>(dev_a, dev_b, dev_c); 60 61 cudaMemcpy(c, dev_c, sizeof c, cudaMemcpyDeviceToHost); 62 cudaFree(dev_a); 63 cudaFree(dev_b); 64 cudaFree(dev_c); 65 } 66 printf("%lld, ", c[1233][1233]); 67 printf("time: %d\n", int(clock()-cstart)); 68 return 0; 69 }
作业2 卷积操作,常量内存
1 //compile command: nvcc cv.cu `pkg-config --cflags --libs opencv` -std=c++11 2 //execute command1: ./a.out CC.jpg 3 3 //execute command2: ./a.out CC.jpg 5 4 #include <bits/stdc++.h> 5 6 #include <opencv2/opencv.hpp> 7 //#include <opencv2/gpu/gpu.hpp> 8 using namespace cv; 9 10 Mat G3 = (Mat_<int>(3, 3) << -5, 3, -5, 11 3, 9, 3, 12 -5, 3, -5); 13 14 Mat G5 = (Mat_<int>(5, 5) << 0, -1, 1, -1, 0, 15 -1, 1, -1, 1,-1, 16 0, -1, 8,-1, 0, 17 -1, 1, -1, 1,-1, 18 0, -1, 1,-1, 0); 19 20 void CPU_Sharpen(const Mat& myImage, Mat& Result, int ca){ 21 CV_Assert(myImage.depth() == CV_8U); // accept only uchar images 22 23 int begin = clock(); 24 Result.create(myImage.size(), myImage.type()); 25 const int nChannels = myImage.channels(); 26 Mat &G = ca == 3? G3: G5; 27 int half = G.rows >> 1; 28 29 for(int row = half; row < myImage.rows-half; ++row) { 30 uchar* output = Result.ptr<uchar>(row); 31 for(int col = half*nChannels; col < nChannels * (myImage.cols - half); ++col) { 32 int tmp = 0; 33 for(int i = 0; i < G.rows; ++i) 34 for(int j = 0; j < G.cols; ++j) 35 tmp += G.at<int>(i, j)*( *(myImage.ptr<uchar>(row-half+i)+(col-half*nChannels+j*nChannels) ) ); 36 *output++ = saturate_cast<uchar>(tmp); 37 } 38 } 39 for(int i = 0; i < half; i++) { 40 Result.row(i).setTo(Scalar(140)); 41 Result.row(Result.rows - 1 - i).setTo(Scalar(140)); 42 Result.col(i).setTo(Scalar(140)); 43 Result.col(Result.cols - 1 - i).setTo(Scalar(140)); 44 } 45 printf("Time used %.3fms\n", ((int)clock()-begin)*1000.0/CLOCKS_PER_SEC); 46 } 47 48 /********************************************/ 49 50 __constant__ int con_G3[3][3] = { 51 {-5, 3, -5}, 52 { 3, 9, 3}, 53 {-5, 3, -5} 54 }; 55 __constant__ int con_G5[5][5] = { 56 {0, -1, 1,-1, 0}, 57 {-1, 1, -1, 1,-1}, 58 {0, -1, 8,-1, 0}, 59 {-1, 1, -1, 1,-1}, 60 {0, -1, 1,-1, 0} 61 }; 62 63 __global__ void init_cuda(uchar *c, int col_num) { 64 int col_id = blockIdx.x, row_id = threadIdx.x; 65 int now = (row_id*col_num+col_id)*3; 66 c[now] = c[now+1] = c[now+2] = 0; 67 } 68 69 70 //GPU,start from c, num * sizeof(uchar) 71 __global__ void test(uchar *c, int *sum, int num) { 72 int x = 0; 73 for(int i = 0; i < num; i++) 74 x += c[i]; 75 *sum = x; 76 } 77 78 __global__ void con_cuda(uchar *s, uchar *t, int ca, int row_num, int col_num) { 79 int col_id = blockIdx.x-1, row_id = threadIdx.x-1; 80 const int half = ca >> 1; 81 if(row_id >= half && row_id < row_num-half && col_id >= half && col_id < col_num-half) { 82 const int* con_mat = ca == 3? con_G3[0]: con_G5[0]; 83 int res[3] = {0, 0, 0}; 84 for(int i = 0; i < ca; i++) 85 for(int j = 0; j < ca; j++) { 86 //s[row_num][col_num][3]; 87 int pos = (row_id-half+i)*col_num*3+(col_id-half+j)*3; 88 res[0] += con_mat[i*ca+j]*s[pos]; 89 res[1] += con_mat[i*ca+j]*s[pos+1]; 90 res[2] += con_mat[i*ca+j]*s[pos+2]; 91 } 92 res[0] = res[0] < 0? 0: (res[0] > 255? 255: res[0]); 93 res[1] = res[1] < 0? 0: (res[1] > 255? 255: res[1]); 94 res[2] = res[2] < 0? 0: (res[2] > 255? 255: res[2]); 95 int pos = row_id*col_num*3+col_id*3; 96 t[pos] = res[0], 97 t[pos+1] = res[1], 98 t[pos+2] = res[2]; 99 } 100 } 101 102 /*******************************************/ 103 104 void HANDLE(cudaError x) { 105 if(x != cudaSuccess) { 106 puts("error!"); 107 exit(0); 108 } 109 } 110 111 int main(int argc, char** argv ) { 112 if ( argc < 3 ) { 113 printf("usage: a.out <Image_Path> <size of Mat>\n"); 114 return -1; 115 } 116 117 Mat src_img = imread(argv[1], 1), ans_CPU, ans_GPU; 118 int ca = argv[2][0]-'0'; 119 printf("%d %d\n", src_img.rows, src_img.cols); 120 121 /**********************************************************************************/ 122 123 printf("Run on CPU!\n"); 124 CPU_Sharpen(src_img, ans_CPU, ca); 125 std::string s = std::string("CC")+std::to_string(ca)+std::string("_With_CPU.jpg"); 126 imwrite(s, ans_CPU); 127 imshow("after operation", ans_CPU); 128 waitKey(); 129 130 /**********************************************************************************/ 131 132 printf("Run on GPU!\n"); 133 int begin = clock(); 134 uchar *dev_src, *dev_result; 135 int seg = src_img.cols*src_img.channels(); 136 HANDLE(cudaMalloc( (void**)&dev_src, src_img.rows*seg*sizeof(uchar))); 137 HANDLE(cudaMalloc( (void**)&dev_result, src_img.rows*seg*sizeof(uchar))); 138 /*Memcpy to dev_src*/ 139 for(int i = 0; i < src_img.rows; ++i) 140 HANDLE(cudaMemcpy(dev_src+i*seg*sizeof(uchar), src_img.ptr<uchar>(i), sizeof(uchar)*seg, cudaMemcpyHostToDevice)); 141 /*Init for dev_result*/ 142 init_cuda<<<src_img.cols, src_img.rows>>>(dev_result, src_img.cols); 143 /*Do convolution*/ 144 con_cuda<<<src_img.cols, src_img.rows>>>(dev_src, dev_result, ca, src_img.rows, src_img.cols); 145 146 ans_GPU.create(src_img.size(), src_img.type()); 147 /*Memcpy to host*/ 148 for(int i = 0; i < ans_GPU.rows; ++i) 149 cudaMemcpy(ans_GPU.ptr<uchar>(i), dev_result+i*seg*sizeof(uchar), sizeof(uchar)*seg, cudaMemcpyDeviceToHost); 150 151 for(int i = 0; i < (ca >> 1); i++) { 152 ans_GPU.row(i).setTo(Scalar(140)); 153 ans_GPU.row(ans_GPU.rows - 1 - i).setTo(Scalar(140)); 154 ans_GPU.col(i).setTo(Scalar(140)); 155 ans_GPU.col(ans_GPU.cols - 1 - i).setTo(Scalar(140)); 156 } 157 /*Free*/ 158 cudaFree(dev_src); 159 cudaFree(dev_result); 160 printf("Time used %.3fms\n", ((int)clock()-begin)*1000.0/CLOCKS_PER_SEC); 161 imshow("after operation", ans_GPU); 162 waitKey(); 163 return 0; 164 }