規約算法

0.引言

有的地方也稱之爲歸約算法.

/* asum: sum of all entries of a vector.
 * This code only calculates one block to show the usage of shared memory and synchronization */

#include <stdio.h>
#include <cuda.h>

typedef double FLOAT;

/* sum all entries in x and asign to y */
__global__ void reduction_1(const FLOAT *x, FLOAT *y)
{
    __shared__ FLOAT sdata[256];
    int tid = threadIdx.x;

    /* load data to shared mem 
    共享內存是塊內線程可見的,所以就有競爭問題的存在,也可以通過共享內存進行通信. 
    爲了避免內存競爭,可以使用同步語句:void __syncthreads();語句相當於在線程
    塊執行時各個線程的一個障礙點,當塊內所有線程都執行到本障礙點的時候才能進行下
    一步的計算;但是,__syncthreads(); 頻繁使用會影響內核執行效率。*/
    sdata[tid] = x[tid];//這個x是 FLOAT *x;
    __syncthreads();

    /* reduction using shared mem 把for循環展開*/
    if (tid < 128) sdata[tid] += sdata[tid + 128];
    __syncthreads();

    if (tid < 64) sdata[tid] += sdata[tid + 64];
    __syncthreads();

    if (tid < 32) sdata[tid] += sdata[tid + 32];
    __syncthreads();

    if (tid < 16) sdata[tid] += sdata[tid + 16];
    __syncthreads();

    if (tid < 8) sdata[tid] += sdata[tid + 8];
    __syncthreads();

    if (tid < 4) sdata[tid] += sdata[tid + 4];
    __syncthreads();

    if (tid < 2) sdata[tid] += sdata[tid + 2];
    __syncthreads();

    if (tid == 0) {
        *y = sdata[0] + sdata[1];
    }
}



__global__ void reduction_2(const FLOAT *x, FLOAT *y)
{
    __shared__ volatile FLOAT sdata[256];//加volatile關鍵字,避免編譯器自己進行優化.
    int tid = threadIdx.x;

    sdata[tid] = x[tid];
    __syncthreads();

    if(tid < 128) sdata[tid] += sdata[tid + 128];
    __syncthreads();

    if(tid < 64) sdata[tid] += sdata[tid + 64];
    __syncthreads();

    
    if(tid < 32) 
    {
        sdata[tid] += sdata[tid + 32];
        sdata[tid] += sdata[tid + 16];
        sdata[tid] += sdata[tid + 8];
        sdata[tid] += sdata[tid + 4];
        sdata[tid] += sdata[tid + 2];
        sdata[tid] += sdata[tid + 1];
    }
    if(tid == 0)  y[0] =sdata[0];

}




//__device__ 只能在GPU上被調用
__device__ void warpReduce(volatile FLOAT *sdata, int tid)
{
    sdata[tid] += sdata[tid + 32];
    sdata[tid] += sdata[tid + 16];
    sdata[tid] += sdata[tid + 8];
    sdata[tid] += sdata[tid + 4];
    sdata[tid] += sdata[tid + 2];
    sdata[tid] += sdata[tid + 1];
}

__global__ void reduction_3(const FLOAT *x, FLOAT *y)
{
    __shared__ FLOAT sdata[256];
    int tid = threadIdx.x;

    /* load data to shared mem */
    sdata[tid] = x[tid];
    __syncthreads();

    /* reduction using shared mem */
    if (tid < 128) sdata[tid] += sdata[tid + 128];
    __syncthreads();

    if (tid < 64) sdata[tid] += sdata[tid + 64];
    __syncthreads();

    if (tid < 32) warpReduce(sdata, tid);

    if (tid == 0) y[0] = sdata[0];
}

int main()
{
    int N = 256;   /* must be 256 */
    int nbytes = N * sizeof(FLOAT);

    FLOAT *dx = NULL, *hx = NULL;
    FLOAT *dy = NULL;
    int i;
    FLOAT as = 0;

    /************** allocate GPU mem ***************/
    cudaMalloc((void **)&dx, nbytes);
    cudaMalloc((void **)&dy, sizeof(FLOAT));
    if (dx == NULL || dy == NULL) {
        printf("couldn't allocate GPU memory\n");
        return -1;
    }
    printf("allocated %e MB on GPU\n", nbytes / (1024.f * 1024.f));



    /**************** alllocate CPU mem ************/
    hx = (FLOAT *) malloc(nbytes);
    if (hx == NULL) {
        printf("couldn't allocate CPU memory\n");
        return -2;
    }
    printf("allocated %e MB on CPU\n", nbytes / (1024.f * 1024.f));



    /****************** init *********************/
    for (i = 0; i < N; i++) {
        hx[i] = 1;
    }

    /* copy data to GPU */
    cudaMemcpy(dx, hx, nbytes, cudaMemcpyHostToDevice);
    /* call GPU */
    reduction_1<<<1, N>>>(dx, dy);
    /* let GPU finish */
    cudaThreadSynchronize();
    /* copy data from GPU */
    cudaMemcpy(&as, dy, sizeof(FLOAT), cudaMemcpyDeviceToHost);
    printf("reduction_1, answer: 256, calculated by GPU:%g\n", as);


    /* call GPU */
    reduction_2<<<1, N>>>(dx, dy);
    /* let GPU finish */
    cudaThreadSynchronize();
    /* copy data from GPU */
    cudaMemcpy(&as, dy, sizeof(FLOAT), cudaMemcpyDeviceToHost);
    printf("reduction_2, answer: 256, calculated by GPU:%g\n", as);


    /* call GPU */
    reduction_3<<<1, N>>>(dx, dy);
    /* let GPU finish */
    cudaThreadSynchronize();
    /* copy data from GPU */
    cudaMemcpy(&as, dy, sizeof(FLOAT), cudaMemcpyDeviceToHost);
    printf("reduction_3, answer: 256, calculated by GPU:%g\n", as);


    cudaFree(dx);
    cudaFree(dy);
    free(hx);

    return 0;
}

1.reduction_1

/* sum all entries in x and asign to y */
__global__ void reduction_1(const FLOAT *x, FLOAT *y)
{
    __shared__ FLOAT sdata[256];
    int tid = threadIdx.x;

    /* load data to shared mem 
    共享內存是塊內線程可見的,所以就有競爭問題的存在,也可以通過共享內存進行通信. 
    爲了避免內存競爭,可以使用同步語句:void __syncthreads();語句相當於在線程
    塊執行時各個線程的一個障礙點,當塊內所有線程都執行到本障礙點的時候才能進行下
    一步的計算;但是,__syncthreads(); 頻繁使用會影響內核執行效率。*/
    sdata[tid] = x[tid];//這個x是 FLOAT *x;
    __syncthreads();

    /* reduction using shared mem 把for循環展開*/
    if (tid < 128) sdata[tid] += sdata[tid + 128];
    __syncthreads();

    if (tid < 64) sdata[tid] += sdata[tid + 64];
    __syncthreads();

    if (tid < 32) sdata[tid] += sdata[tid + 32];
    __syncthreads();

    if (tid < 16) sdata[tid] += sdata[tid + 16];
    __syncthreads();

    if (tid < 8) sdata[tid] += sdata[tid + 8];
    __syncthreads();

    if (tid < 4) sdata[tid] += sdata[tid + 4];
    __syncthreads();

    if (tid < 2) sdata[tid] += sdata[tid + 2];
    __syncthreads();

    if (tid == 0) {
        *y = sdata[0] + sdata[1];
    }
}

使用__share__共享內存以及__syncthreads()函數進行斷句(斷句爲自己理解命名的).線程理解:
Alt

Alt

2.reduction_2

__global__ void reduction_2(const FLOAT *x, FLOAT *y)
{
    __shared__ volatile FLOAT sdata[256];//加volatile關鍵字,避免編譯器自己進行優化.
    int tid = threadIdx.x;

    sdata[tid] = x[tid];
    __syncthreads();

    if(tid < 128) sdata[tid] += sdata[tid + 128];
    __syncthreads();

    if(tid < 64) sdata[tid] += sdata[tid + 64];
    __syncthreads();

    
    if(tid < 32) 
    {
        sdata[tid] += sdata[tid + 32];
        sdata[tid] += sdata[tid + 16];
        sdata[tid] += sdata[tid + 8];
        sdata[tid] += sdata[tid + 4];
        sdata[tid] += sdata[tid + 2];
        sdata[tid] += sdata[tid + 1];
    }
    if(tid == 0)  y[0] =sdata[0];

}

這個算法注意點在於:

對於第一點,第二節中已經介紹了;對於第二點,GPU中最小的操作爲一個warp,一個warp32個線程,這也是程序從if(tid < 32)開始並列寫的原因.對於第三點,我嘗試用自己的語音講出來,但是發現還是官方的說法最爲貼切中肯,具體的可以參考鏈接中的文章:

  • volatile關鍵字可以實現線程間的可見性,之所以可以實現這一點,原因在於JVM會保證被volatile修飾的變量,在線程棧中被線程使用時都會主動從共享內存(堆內存/主內存)中以實時的方式同步一次;另一方面,如果線程在工作內存中修改了volatile修飾的變量,也會被JVM要求立馬刷新到共享內存中去。因此,即便某個線程修改了該變量,其他線程也可以立馬感知到變化從而實現可見性.

大意就是被volatile修飾的變量是實時更新的(可以相反於中斷理解),不會被編譯器優化誤解算法思想.

Alt

3.reduction_3

//__device__ 只能在GPU上被調用
__device__ void warpReduce(volatile FLOAT *sdata, int tid)
{
    sdata[tid] += sdata[tid + 32];
    sdata[tid] += sdata[tid + 16];
    sdata[tid] += sdata[tid + 8];
    sdata[tid] += sdata[tid + 4];
    sdata[tid] += sdata[tid + 2];
    sdata[tid] += sdata[tid + 1];
}

__global__ void reduction_3(const FLOAT *x, FLOAT *y)
{
    __shared__ FLOAT sdata[256];
    int tid = threadIdx.x;

    /* load data to shared mem */
    sdata[tid] = x[tid];
    __syncthreads();

    /* reduction using shared mem */
    if (tid < 128) sdata[tid] += sdata[tid + 128];
    __syncthreads();

    if (tid < 64) sdata[tid] += sdata[tid + 64];
    __syncthreads();

    if (tid < 32) warpReduce(sdata, tid);

    if (tid == 0) y[0] = sdata[0];
}

reduction_3思想與reduction_2一樣,只是爲了理解volatile的修飾作用.

4.result

Alt

去除reduction_2中的volatile關鍵字結果:
Alt

28,結果肯定是小於256的.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章