一、前言
本文對使用cuda加速一維數組運算、二維圖像處理運算的方法作總結,參考自《CUDA By Example》
二、一維數組并行運算
經過查詢,本人的老顯卡GT550M,可得其3維線程格,每維包含線程塊數量為(65536,65536,65536),相應的每維包含線程數為(1024,1024,64),故可得知線程格的每一維可開啟的線程塊最大數均為65536,相應線程的最大數為(1024,1024,64)。
1)以下為對一維數組并行運算的例子,使用了3種基本方法
//main.cu#include <cuda_runtime.h> #include <iostream> #include "book.h" //該頭文件定義了HANDLE_ERROR函數using namespace std;int mode = 0 ;#define N 1024void select(){ cout << "select the calculation mode:/n" << "1 -> only by blockIdx/n" << "2 -> only by threadIdx/n" << "3 -> by blockIdx and threadIdx /n"; cout << "mode = "; cin >> mode; cout << endl;}__global__ void add_only_blockIdx(int *a, int *b, int *c) //__global表示該函數可在主機調用,在器件執行;{ // 另外__device__表示該函數在器件調用,在期間執行 int idx = blockIdx.x; //計算位于線程塊索引處的數據 if (idx < N){ c[idx] = a[idx] + b[idx]; }}__global__ void add_only_threadIdx(int *a, int *b, int *c) { int idx = threadIdx.x; //計算位于線程索引處的數據 if (idx < N){ c[idx] = a[idx] + b[idx]; }}__global__ void add_blockIdx_threadIdx(int *a, int *b, int *c) { int idx = threadIdx.x + blockIdx.x * blockDim.x; //將線程塊索引與線程索引轉為線性 if (idx < N){ c[idx] = a[idx] + b[idx]; }}int main(){ while ( mode< 1 || mode >3 ){ select(); } int a[N], b[N], c[N]; int *dev_a, *dev_b, *dev_c; //gpu上分配內存 HANDLE_ERROR(cudaMalloc((void**)&dev_a, N*sizeof(int))); HANDLE_ERROR(cudaMalloc((void**)&dev_b, N*sizeof(int))); HANDLE_ERROR(cudaMalloc((void**)&dev_c, N*sizeof(int))); //為數組a,b初始化 for (int i = 0; i < N; ++i){ a[i] = i; b[i] = i; } //講數組a,b數據復制至gpu (cudaMemcpy(dev_a, a, N*sizeof(int), cudaMemcpyHostToDevice)); (cudaMemcpy(dev_b, b, N*sizeof(int), cudaMemcpyHostToDevice)); if (mode == 1) add_only_blockIdx << < N, 1 >> >(dev_a, dev_b, dev_c); //使用N個線程塊,其中每個線程塊使用1個線程,這里N小于65536,故可行 else if (mode == 2) add_only_threadIdx << < 1, N >> >(dev_a, dev_b, dev_c); //使用1個線程塊,其中該線程塊使用1024個線程,這里N小于1024,故可行 else add_blockIdx_threadIdx << < (N+127)/128, 128 >> >(dev_a, dev_b, dev_c); // 這里使用(N+127)/128個線程塊,每個線程塊128線程,為了 // 使所 (N+127)/128 * 128 >= N //將數組dev_c復制至cpu HANDLE_ERROR(cudaMemcpy(c, dev_c, N*sizeof(int), cudaMemcpyDeviceToHost)); //顯示結果 for (int i = 0; i < N; ++i){ PRintf("%d + %d = %d/n", a[i], b[i], c[i]); } //釋放在gpu分配的內存 cudaFree(dev_a); cudaFree(dev_b); cudaFree(dev_c); return 0;}2)對任意長度的一維數組并行求和運算
當一維數組長度大于65536*1024= 67108864 時,使用(1)的方法則不可行,因為線程格的每維最大線程塊不大于65536,且每維的線程不大于1024(第3維除外),故可使用如下方法。
//main2.cu#include <cuda_runtime.h> #include <iostream> #include "book.h" //該頭文件定義了HANDLE_ERROR函數using namespace std;#define N 33*1024 //在gpu運算的數組長度受GPU內存的限制,故這里使用33*1024表示長度較大的數組__global__ void add(int *a, int *b, int *c) { int idx = threadIdx.x + blockIdx.x * blockDim.x ; //所計算的索引為 128個線程塊中的128個線程索引 // 由于128*128 < 33*1024,故某些線程需執行多次運算 while (idx < N){ c[idx] = a[idx] + b[idx]; idx += gridDim.x * blockDim.x; //對索引進行遞增,遞增步長為gridDim.x * blockDim.x,gridDim.x為使用的線程塊總數, } //blockDim.x為使用的每個線程塊中的線程總數}int main(){ int a[N], b[N], c[N]; int *dev_a, *dev_b, *dev_c; //gpu上分配內存 HANDLE_ERROR(cudaMalloc((void**)&dev_a, N*sizeof(int))); HANDLE_ERROR(cudaMalloc((void**)&dev_b, N*sizeof(int))); HANDLE_ERROR(cudaMalloc((void**)&dev_c, N*sizeof(int))); //為數組a,b初始化 for (int i = 0; i < N; ++i){ a[i] = i; b[i] = i; } //講數組a,b數據復制至gpu (cudaMemcpy(dev_a, a, N*sizeof(int), cudaMemcpyHostToDevice)); (cudaMemcpy(dev_b, b, N*sizeof(int), cudaMemcpyHostToDevice)); add <<< 128, 128 >>>(dev_a, dev_b, dev_c); //使用128個線程塊,每個線程塊使用128個線程,128*128 < 33*1024 //將數組dev_c復制至cpu HANDLE_ERROR(cudaMemcpy(c, dev_c, N*sizeof(int), cudaMemcpyDeviceToHost)); //顯示結果 for (int i = 0; i < N; ++i){ printf("%d + %d = %d/n", a[i], b[i], c[i]); } //釋放在gpu分配的內存 cudaFree(dev_a); cudaFree(dev_b); cudaFree(dev_c); return 0;}三、二維圖像處理并行運算 對二維圖像數據進行并行運算,這里使用經典的Reduce Color例子,其對圖像中的每個像素點進行量化,如常見的RGB24圖像有256×256×256中顏色,通過Reduce Color將每個通道的像素減少8倍至256/8=32種,則圖像只有32×32×32種顏色。假設量化減少的倍數是N,則代碼實現時就是簡單的value/N*N,通常我們會再加上N/2以得到相鄰的N的倍數的中間值。
//main3.cu#include <cuda_runtime.h> #include <iostream> #include "book.h" //該頭文件定義了HANDLE_ERROR函數#include <opencv2/opencv.hpp>using namespace std;using namespace cv;__global__ void quantify(uchar *_src_dev, uchar *_dst_dev , int div){ int x_idx = threadIdx.x + blockIdx.x*blockDim.x; //計算x坐標索引 int y_idx = threadIdx.y + blockIdx.y*blockDim.y; //計算y坐標索引 int idx = x_idx + y_idx * gridDim.x * blockDim.x; //將(x,y)坐標轉換為線性 _dst_dev[idx ] = _src_dev[idx] / div*div + div / 2;}int main(){ Mat src = imread("lena.bmp" ); //讀取圖像 Mat dst (src.size(), CV_8UC3); uchar *src_data = src.data; // src_data指針指向圖像src的數據 if (!src.isContinuous()) return -1; //判斷圖像數據字節是否填充 uchar *src_dev, *dst_dev; int length = src.rows * src.cols * src.channels(); //在gpu上分配內存空間 HANDLE_ERROR(cudaMalloc((void**)&src_dev, length*sizeof(uchar))); HANDLE_ERROR(cudaMalloc((void**)&dst_dev, length*sizeof(uchar))); HANDLE_ERROR(cudaMemcpy(src_dev, src_data, length*sizeof(uchar), cudaMemcpyHostToDevice)); dim3 blocks(src.cols*src.channels()/ 32, src.rows/ 32); //寬和高均為512,所啟動線程塊與線程數量差距不可過大 dim3 threads(32, 32); quantify << < blocks, threads >> >(src_dev, dst_dev , 64); //將數組dst_dev復制至cpu HANDLE_ERROR(cudaMemcpy(dst.data, dst_dev, length*sizeof(uchar), cudaMemcpyDeviceToHost)); cudaFree(src_dev); cudaFree(dst_dev); imshow("src", src); imshow("Dst", dst); waitKey(0); return 0;}運行結果
新聞熱點
疑難解答