首先是CPU和GPU的比較, 這裡先簡略帶過, 之後有機會再來寫個一篇(是要挖多少坑....), 簡單來說GPU和CPU比較起來, GPU有非常多的核心(core)可以一次做很多事情, 因此如果需要大量計算, 且每次計算結果間彼此獨立的話, 可以使用GPU來同時進行運算, 這個概念被稱為是平行運算(parallel programming)
這樣講還是有點抽象, 因此用向量加法來作例子, 舉例有兩個向量, A=(1,2,3), B=(3,4,5), 要把兩個矩陣相加, 如果一一相加的話, 要加三次, 但如果分給GPU做, 雖然還是要做三次加法, 但三次加法可以同時做, 因此完成的時間變短, 這就是平行運算的精神
CUDA就是Nvidia拿來整合大家熟悉的程式語言(C,C++,java, fortan....), 和GPU間的平台
由於小弟寫比較多的是C/C++語言, 而且官方文件也是用C/C++語言, 因此以下用C/C++語言語法來介紹,
CUDA裡面有所謂的 kernel 概念, kernel內的程式碼是由GPU執行, 用 __global__ 做宣告(注意底標的橫槓有兩個), 這裡參考一下官方文件, 寫法如下:
//目標是在GPU裡面把兩個陣列相加
__global__ void add( int *a, int *b, int *c ) { int i = threadIdx.x;//threadIdx是CUDA內建用來指向特定thread的變數 c[i]=a[i]+b[i]; }
P.S. 在這裡可能有些人會想說什麼是thread, thread 中文譯為執行緒, 在作業系統的課本會花一大堆時間講,不過這裡簡單來說, 可以把thread想成電腦執行一件事情,就稱為thread
看完了GPU的code, 接著來看主程式怎麼和GPU互動
這裡其實有點複雜, 因為牽扯到兩個部分, 第一個是CUDA如何在主程式裡面表示thread, 第二個部分是CPU和GPU彼此間記憶體如何操作, 先看第一部分:
前面提到的threadIdx其實是一個三維的向量, 每組數字代表一個thread; 一群thread可以組成一個block(目前技術一個block最多可以有但1024個thread), 一群block可以組成一個grid; CUDA有一個特殊的語法, add<<<numBlocks, threadsPerBlock>>>(a, b, c); 如果我們不看<<< >>>裡面的內容的話,會發現就和一般的C語言函數一樣, 而加上的部份就是代表CUDA指定要用這麼多的執行緒去執行程式, 寫出來如下:
#include <iostream> #define N 100 #define numBlocks 1 #define threadsPerBlock 100
__global__ void add( int *a, int *b, int *c ) { int i = threadIdx.x; c[i]=a[i]+b[i]; } int main(void ) { //處理記憶體的部分,待補 add<<<numBlocks, threadsPerBlock>>>(a, b, c); //處理記憶體的部分,待補 }
寫到這裡, 其實程式還是不會動, 要讓程式會動還要處理記憶體的部份,沒錯大家害怕的指標(pointer)要出現惹, 當然還有超級煩人的動態記憶體配置, malloc!!!
在CUDA裡面, 分為host和device, host一般指得是用CPU處理, device一般指得是用GPU處理, 彼此間的記憶體並不共用, 因此在運算的時候, 要把host記憶體的東西複製給device, 算完後再複製回來, 在這裡為了方便辨識, host的變數都加上h_, device的變數都加上d_, 程式碼如下:
#include <iostream> #include <cstdlib> #include <ctime> #define N 100 #define numBlocks 1 #define threadsPerBlock 100 using namespace std; __global__ void add( int *a, int *b, int *c ) { int i = threadIdx.x; c[i]=a[i]+b[i]; } int main( void ) { int *h_a, *h_b, *h_c; int *d_a, *d_b, *d_c; // 在host分配記憶體空間 h_a = (int*)malloc( N * sizeof(int) ); h_b = (int*)malloc( N * sizeof(int) ); h_c = (int*)malloc( N * sizeof(int) ); // 亂數指定值給陣列 for (int i=0; i<N; i++) { h_a[i] = rand(); h_b[i] = rand(); } // 在device上分配記憶體 cudaMalloc( (void**)&d_a, N * sizeof(int) ); cudaMalloc( (void**)&d_b, N * sizeof(int) ); cudaMalloc( (void**)&d_c, N * sizeof(int) ); // 把host的東西複製到device上 cudaMemcpy( d_a, h_a, N * sizeof(int), cudaMemcpyHostToDevice ); cudaMemcpy( d_b, h_b, N * sizeof(int), cudaMemcpyHostToDevice ); add<<<numBlocks, threadsPerBlock>>>( d_a, d_b, d_c ); // 把device算出來的結果複製回CPU cudaMemcpy( h_c, d_c, N * sizeof(int),cudaMemcpyDeviceToHost ); //驗算 int ok=1; for(int i=0;i<N;i++) { if(h_c[i]!=h_a[i]+h_b[i]) { cout<<"fail"; ok=0; break; } } if(ok==1) printf("success"); // 釋放host的記憶體 free( h_a ); free( h_b ); free( h_c ); // 釋放device的記憶體 cudaFree( d_a ); cudaFree( d_b ); cudaFree( d_c ); return 0; }
reference:
1. Nvidia developer zone
2. http://selkie.macalester.edu/csinparallel/modules/ConceptDataDecomposition/build/html/Decomposition/CUDA_VecAdd.html
沒有留言:
張貼留言