點積運算--GPU高性能編程CUDA實戰5.3



這裏用點積運算例子是在講線程協作的 __syncthread();

表示對線程塊裏的線程進行同步的,對於線程塊之間的沒有關係的。在線程塊內所有的線程必須同時停到這個地方等待,等到所有的線程都運行到這裏的時候,再一起運行下一步。這樣做的目的是因爲某個線程要用到別的線程的計算之後的結果,如果不設置__syncthread();將會計算出錯誤的結果。這個中文名叫柵欄。剛纔提到了用到別的線程的計算結果,這裏又涉及到線程間的通訊,一個線程塊裏都有個共享的內存,實現線程之間的通訊,用__share__調用。還是在代碼裏寫註釋更好理解,我就先在代碼裏寫註釋好了,我不習慣這個編輯器,哈哈


#include "../common/book.h"

#define imin(a,b) (a<b?a:b)

const int N = 33 * 1024;
線程塊裏的線程256個,線程格一共有32個線程,這就意味着,每個線程將會計算4次,因爲數組元素很大
const int threadsPerBlock = 256;
至今爲止我也沒見過兩個線程格的,這裏寫的意思是看看每個線程塊用256個線程的話能用到多少個線程塊,但是不要超過32個,但是很明顯超過了32個好嘛,所以就32個了唄,說明線程個數是不夠的
const int blocksPerGrid =
            imin( 32, (N+threadsPerBlock-1) / threadsPerBlock );


__global__ void dot( float *a, float *b, float *c ) {
	共享內存說明是在函數內部聲明的,一個線程執行一個函數副本的話,一開始我就覺得,是不是這句話會運行好多好多遍,該不該放外面,後來想想,GPU會搞定的,肯定放裏面,怎麼調度我就不管了
    __shared__ float cache[threadsPerBlock];
	線程索引的話,這裏是一維線程格,線程塊也是一維的,比較簡單
    int tid = threadIdx.x + blockIdx.x * blockDim.x;
	cache內索引的目的是進行歸約計算的,這個線程塊有256個線程,每個線程計算之後的結果都會放在這個cache裏,不同線程塊之間的cache是不一樣的,也就是說,一個線程塊裏的線程公用一個cache,反之不共用
    int cacheIndex = threadIdx.x;
	//下面temp是保存一個線程裏所有計算乘積之後的加和,因爲剛纔說過這裏線程不夠用,要多次計算
    float   temp = 0;
    while (tid < N) {
        temp += a[tid] * b[tid];
        tid += blockDim.x * gridDim.x;
    }
    那麼當這個線程計算完畢之後,這個值就會放在這個有256個元素的一維數組裏了
     set the cache values
    cache[cacheIndex] = temp;
    接下來進行歸約計算呢,需要線程之間的計算結果相加(同一個線程塊),所以,要耐心等待所有的線程都完成了計算,所以這裏設置 了一個柵欄
     synchronize threads in this block
    __syncthreads();

    //for reductions, threadsPerBlock must be a power of 2
    //because of the following code
    int i = blockDim.x/2;
    while (i != 0) {
        if (cacheIndex < i)
這裏就是吧cache裏所有的元素相加,空間複雜度是logN,挺小的,但是這裏後來個問題,既然是個判斷語句,就有肯定不執行這條語句的線程,但是爲何不在判斷語句內設置個柵欄,把不參與計算的線程過掉,對吧
這是個很好的想法,但是如果這樣做,系統就會停止運算,因爲會陷入無限等待,在這個線程同步的過程中,要求是所有的線程都要進行同步,不管參沒參與運算,如果像剛纔那麼做,會造成一個叫“線程發散”的現象
            cache[cacheIndex] += cache[cacheIndex + i];
        __syncthreads();
        i /= 2;
    }
	cache最後的結果是在第0個位置上的,所以把這個傳給數組c裏(這裏是形參c,實參是dev_partial_c),數組c是32個元素,標誌着32個線程塊的每一個結果分別放在指定的位置上,然後在CPU上執行計算,原因是,這裏我們有大量的線程塊
	在運算,32*256個,如果只是對一個有32個元素的數組進行加法運算的時候,肯定有大量的線程是閒着的,這就造成了資源的浪費,所以不如把它拿給CPU去做,讓GPU騰出時間去幹些更高大上的事情,這裏的main函數裏就是這樣做的
    if (cacheIndex == 0)
        c[blockIdx.x] = cache[0];
}

 main函數裏沒有什麼不同和之前的,最後的計算結果呢,是比較的來着,就是這個可以直接用數學公式計算出來的,就是看看最後的數據對不對,別的就沒什麼可寫的了
int main( void ) {
    float   *a, *b, c, *partial_c;
    float   *dev_a, *dev_b, *dev_partial_c;

    // allocate memory on the cpu side
    a = (float*)malloc( N*sizeof(float) );
    b = (float*)malloc( N*sizeof(float) );
    partial_c = (float*)malloc( blocksPerGrid*sizeof(float) );

    // allocate the memory on the GPU
    HANDLE_ERROR( cudaMalloc( (void**)&dev_a,
                              N*sizeof(float) ) );
    HANDLE_ERROR( cudaMalloc( (void**)&dev_b,
                              N*sizeof(float) ) );
    HANDLE_ERROR( cudaMalloc( (void**)&dev_partial_c,
                              blocksPerGrid*sizeof(float) ) );

    // fill in the host memory with data
    for (int i=0; i<N; i++) {
        a[i] = i;
        b[i] = i*2;
    }

    // copy the arrays 'a' and 'b' to the GPU
    HANDLE_ERROR( cudaMemcpy( dev_a, a, N*sizeof(float),
                              cudaMemcpyHostToDevice ) );
    HANDLE_ERROR( cudaMemcpy( dev_b, b, N*sizeof(float),
                              cudaMemcpyHostToDevice ) ); 

    dot<<<blocksPerGrid,threadsPerBlock>>>( dev_a, dev_b,
                                            dev_partial_c );

    // copy the array 'c' back from the GPU to the CPU
    HANDLE_ERROR( cudaMemcpy( partial_c, dev_partial_c,
                              blocksPerGrid*sizeof(float),
                              cudaMemcpyDeviceToHost ) );

    // finish up on the CPU side
    c = 0;
    for (int i=0; i<blocksPerGrid; i++) {
        c += partial_c[i];
    }

    #define sum_squares(x)  (x*(x+1)*(2*x+1)/6)
    printf( "Does GPU value %.6g = %.6g?\n", c,
             2 * sum_squares( (float)(N - 1) ) );
	getchar();
    // free memory on the gpu side
    HANDLE_ERROR( cudaFree( dev_a ) );
    HANDLE_ERROR( cudaFree( dev_b ) );
    HANDLE_ERROR( cudaFree( dev_partial_c ) );

    // free memory on the cpu side
    free( a );
    free( b );
    free( partial_c );
}


這是運行結果,證明這樣做還不錯

發佈了27 篇原創文章 · 獲贊 7 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章