天天看點

GPU 程式設計入門到精通(五)之 GPU 程式優化進階

部落客由于工作當中的需要,開始學習 GPU 上面的程式設計,主要涉及到的是基于 GPU 的深度學習方面的知識,鑒于之前沒有接觸過 GPU 程式設計,是以在這裡特地學習一下 GPU 上面的程式設計。有志同道合的小夥伴,歡迎一起交流和學習,我的郵箱: [email protected] 。使用的是自己的老古董筆記本上面的 Geforce 103m 顯示卡,雖然顯示卡相對于現在主流的系列已經非常的弱,但是對于學習來說,還是可以用的。本系列博文也遵從由簡單到複雜,記錄自己學習的過程。

0. 目錄

  • GPU 程式設計入門到精通(一)之 CUDA 環境安裝
  • GPU 程式設計入門到精通(二)之 運作第一個程式
  • GPU 程式設計入門到精通(三)之 第一個 GPU 程式
  • GPU 程式設計入門到精通(四)之 GPU 程式優化
  • GPU 程式設計入門到精通(五)之 GPU 程式優化進階

1. 數組平方和并行化進階

GPU 程式設計入門到精通(四)之 GPU 程式優化 這篇博文中提到了 grid、block、thread 三者之間的關系,知道了他們之間是逐漸包含的關系。我們在上面的程式中通過使用 512 個線程達到了 493 倍左右的性能提升,那麼是不是可以繼續得到提升呢???

答案是肯定的,這就要進一步考慮 GPU 的并行化處理了。前面的程式隻是使用了單個 block 下的 512 個線程,那麼,我們可不可以使用多個 block 來實作???

對,就是利用這個思想,達到進一步的并行化。這裡使用 8 個 block * 64 threads = 512 threads 實作。

  • 首先,修改主函數宏定義,定義塊數量:
    // ======== define area ========
      #define DATA_SIZE 1048576    // 1M
      #define BLOCK_NUM 8        // block num
      #define THREAD_NUM 64        // thread num
               
    通過在程式中添加 block 和 threads 的宏定義,這兩個定義是我們在後面會用到的。他們決定了計算平方和使用的 CUDA 核心數。
               
  • 接下來,修改核心函數:
    _global__ static void squaresSum(int *data, int *sum, clock_t *time)
      {
          const int tid = threadIdx.x;
          const int bid = blockIdx.x;
    
          for (int i = bid * THREAD_NUM + tid; i < DATA_SIZE; i += BLOCK_NUM * THREAD_NUM) {
              tmp_sum += data[i] * data[i];
          }
    
          sum[bid * THREAD_NUM + tid] = tmp_sum;
      }
               
    注意:這裡的記憶體周遊方式和前面講的是一緻的,了解一下。
    
      同時記錄的時間是一個塊的開始和結束時間,因為這裡我們最後需要計算的是最早開始和最晚結束的兩個時間差,即求出最糟糕的時間。
               
  • 然後,就是主函數裡面的具體實作了:
    // malloc space for datas in GPU
      cudaMalloc((void**) &sum, sizeof(int) * THREAD_NUM * BLOCK_NUM);
    
      // calculate the squares's sum
      squaresSum<<<BLOCK_NUM, THREAD_NUM, >>>(gpuData, sum, time);
               
    這裡邊,sum 數組的長度計算方式變化了,但是大小沒有變化。另在在調用 GPU 核心函數的時候,參數發生了變化,需要告訴 GPU block 數 和 thread 數。不過這邊共享記憶體沒有使用。
               
  • 最後,在 CPU 中計算部分和
    // print result
      int tmp_result = ;
      for (int i = ; i < THREAD_NUM * BLOCK_NUM; ++i) {
          tmp_result += result[i];
      }
               
編譯運作以後,得到如下結果:
GPU 程式設計入門到精通(五)之 GPU 程式優化進階
性能與直接使用 512 個線程基本一緻。因為受到 GPU 記憶體帶寬的限制,GPU 程式設計入門到精通(四)之 GPU 程式優化 中的優化,已經接近極限,是以通過 block 方式,效果不明顯。

2. 線程同步和共享記憶體

前面的程式,計算求和的工作在 CPU 中完成,總共需要在 CPU 中做 512 次加法運算,那麼有沒有辦法減少 CPU 中執行加法的次數呢???

可以通過同步和共享記憶體技術,實作在 GPU 上的 block 塊内求取部分和,這樣最後隻需要在 CPU 計算 16 個和就可以了。具體實作方法如下:

  • 首先,在修改核心函數,定義一塊共享記憶體,用

    __shared__

    訓示:
    __global__ static void squaresSum(int *data, int *sum, clock_t *time)
      {
          // define of shared memory
          __shared__ int shared[BLOCK_NUM];
    
          const int tid = threadIdx.x;
          const int bid = blockIdx.x;
    
          if (tid == ) time[bid] = clock();
    
          shared[tid] = ;
          // 把部分和結果放入共享記憶體中
          for (int i = bid * THREAD_NUM + tid; i < DATA_SIZE; i += BLOCK_NUM * THREAD_NUM) {
              shared[tid] += data[i] * data[i];
          }
          // 同步操作,必須等之前的線程都運作結束,才能繼續後面的程式
          __syncthreads();
          // 同步完成之後,将部分和加到 shared[0] 上面,這裡全都在一個線程内完成
          if (tid == ) {
              for (int i = ; i < THREAD_NUM; i++) {
                  shared[] += shared[i];
              }
              sum[bid] = shared[];
          }
    
          if (tid == ) time[bid + BLOCK_NUM] = clock();
      }
               
    利用 __shared__ 聲明的變量是 shared memory,每個 block 中,各個 thread 之間對于共享記憶體是共享的,利用的是 GPU 上的記憶體,是以速度很快,不必擔心 latency 的問題。
    
      __syncthreads() 函數是 CUDA 的内部函數,表示所有 threads 都必須同步到這個點,才會執行接下來的代碼。我們要做的就是等待每個 thread 計算結束以後,再來計算部分和,是以同步是必不可少的環節。把每個 block 的部分和計算到 shared[0] 裡面。
               
  • 接下來,修改 main 函數:
    // calculate the squares's sum
      squaresSum<<<BLOCK_NUM, THREAD_NUM, THREAD_NUM * sizeof(int)>>>(gpuData, sum, time);
               
    編譯運作後結果如下:
    GPU 程式設計入門到精通(五)之 GPU 程式優化進階
    其實和前一版程式相比,時間上沒有什麼優勢,原因在于,我們需要在 GPU 中額外運作求和的這部分代碼,導緻了運作周期的變長,不過相應的,在 CPU 中的運作時間會減少。

3. 加法樹

我們在這個程式中,隻當每個 block 的 thread0 的時候,計算求和的工作,這樣做影響了執行的效率,其實求和可以并行化處理的,也就是通過加法樹來實作并行化。舉個例子,要計算 8 個數的和,我們沒必要用一個 for 循環,逐個相加,而是可以通過第一級流水線實作兩兩相加,變成 4 個數,第二級流水實作兩兩相加,變成 2 個數,第三級流水實作兩兩相加,求得最後的和。

下面通過加法樹的方法,實作最後的求和,修改核心函數如下:

__global__ static void squaresSum(int *data, int *sum, clock_t *time)
{
    __shared__ int shared[BLOCK_NUM];
    const int tid = threadIdx.x;
    const int bid = blockIdx.x;

    int offset = THREAD_NUM / ;

    if (tid == ) time[bid] = clock();

    shared[tid] = ;

    for (int i = bid * THREAD_NUM + tid; i < DATA_SIZE; i += BLOCK_NUM * THREAD_NUM) {
        shared[tid] += data[i] * data[i];
    }

    __syncthreads();
    while (offset > ) {
        if (tid < offset) {
            shared[tid] += shared[tid + offset];
        }
        offset >>= ;
        __syncthreads();
    }

    if (tid == ) {
        sum[bid] = shared[];
        time[bid + BLOCK_NUM] = clock();
    }
}
           
此程式實作的就是上訴描述的加法樹的結構,注意這裡第二個 __syncthreads() 的使用,也就是說,要進行下一級流水線的計算,必須建立在前一級必須已經計算完畢的情況下。
           
主函數部分不許要修改,最後編譯運作結果如下:
GPU 程式設計入門到精通(五)之 GPU 程式優化進階
性能有一部分的改善。
通過使用 GPU 的并行化程式設計,确實對性能會有很大程度上的提升。由于受限于 Geforce 103m 的記憶體帶寬,程式隻能優化到這一步,關于是否還有其他的方式優化,有待進一步學習。
           

4. 總結

通過這幾篇博文的讨論,數組平方和的代碼優化到這一階段。從但線程到多線程,再到共享記憶體,通過使用這幾種 GPU 上面的結構,做到了程式的優化。如下給出數組平方和的完整代碼:

/* *******************************************************************
##### File Name: squareSum.cu
##### File Func: calculate the sum of inputs's square
##### Author: Caijinping
##### E-mail: [email protected]
##### Create Time: 2014-5-7
* ********************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <cuda_runtime.h>

// ======== define area ========
#define DATA_SIZE 1048576    // 1M
#define BLOCK_NUM 8            // block num
#define THREAD_NUM 64        // thread num

// ======== global area ========
int data[DATA_SIZE];

void printDeviceProp(const cudaDeviceProp &prop);
bool InitCUDA();
void generateData(int *data, int size);
__global__ static void squaresSum(int *data, int *sum, clock_t *time);

int main(int argc, char const *argv[])
{
    // init CUDA device
    if (!InitCUDA()) {
        return ;
    }
    printf("CUDA initialized.\n");

    // generate rand datas
    generateData(data, DATA_SIZE);

    // malloc space for datas in GPU
    int *gpuData, *sum;
    clock_t *time;
    cudaMalloc((void**) &gpuData, sizeof(int) * DATA_SIZE);
    cudaMalloc((void**) &sum, sizeof(int) * BLOCK_NUM);
    cudaMalloc((void**) &time, sizeof(clock_t) * BLOCK_NUM * );
    cudaMemcpy(gpuData, data, sizeof(int) * DATA_SIZE, cudaMemcpyHostToDevice);

    // calculate the squares's sum
    squaresSum<<<BLOCK_NUM, THREAD_NUM, THREAD_NUM * sizeof(int)>>>(gpuData, sum, time);

    // copy the result from GPU to HOST
    int result[BLOCK_NUM];
    clock_t time_used[BLOCK_NUM * ];
    cudaMemcpy(&result, sum, sizeof(int) * BLOCK_NUM, cudaMemcpyDeviceToHost);
    cudaMemcpy(&time_used, time, sizeof(clock_t) * BLOCK_NUM * , cudaMemcpyDeviceToHost);

    // free GPU spaces
    cudaFree(gpuData);
    cudaFree(sum);
    cudaFree(time);

    // print result
    int tmp_result = ;
    for (int i = ; i < BLOCK_NUM; ++i) {
        tmp_result += result[i];
    }

    clock_t min_start, max_end;
    min_start = time_used[];
    max_end = time_used[BLOCK_NUM];
    for (int i = ; i < BLOCK_NUM; ++i)    {
        if (min_start > time_used[i]) min_start = time_used[i];
        if (max_end < time_used[i + BLOCK_NUM]) max_end = time_used[i + BLOCK_NUM];
    }
    printf("(GPU) sum:%d time:%ld\n", tmp_result, max_end - min_start);

    // CPU calculate
    tmp_result = ;

    for (int i = ; i < DATA_SIZE; ++i)    {
        tmp_result += data[i] * data[i];
    }

    printf("(CPU) sum:%d\n", tmp_result);

    return ;
}

__global__ static void squaresSum(int *data, int *sum, clock_t *time)
{
    __shared__ int shared[BLOCK_NUM];
    const int tid = threadIdx.x;
    const int bid = blockIdx.x;

    int offset = THREAD_NUM / ;

    if (tid == ) time[bid] = clock();

    shared[tid] = ;

    for (int i = bid * THREAD_NUM + tid; i < DATA_SIZE; i += BLOCK_NUM * THREAD_NUM) {
        shared[tid] += data[i] * data[i];
    }

    __syncthreads();
    while (offset > ) {
        if (tid < offset) {
            shared[tid] += shared[tid + offset];
        }
        offset >>= ;
        __syncthreads();
    }

    if (tid == ) {
        sum[bid] = shared[];
        time[bid + BLOCK_NUM] = clock();
    }
}

// ======== used to generate rand datas ========
void generateData(int *data, int size)
{
    for (int i = ; i < size; ++i) {
        data[i] = rand() % ;
    }
}


void printDeviceProp(const cudaDeviceProp &prop)
{
    printf("Device Name : %s.\n", prop.name);
    printf("totalGlobalMem : %d.\n", prop.totalGlobalMem);
    printf("sharedMemPerBlock : %d.\n", prop.sharedMemPerBlock);
    printf("regsPerBlock : %d.\n", prop.regsPerBlock);
    printf("warpSize : %d.\n", prop.warpSize);
    printf("memPitch : %d.\n", prop.memPitch);
    printf("maxThreadsPerBlock : %d.\n", prop.maxThreadsPerBlock);
    printf("maxThreadsDim[0 - 2] : %d %d %d.\n", prop.maxThreadsDim[], prop.maxThreadsDim[], prop.maxThreadsDim[]);
    printf("maxGridSize[0 - 2] : %d %d %d.\n", prop.maxGridSize[], prop.maxGridSize[], prop.maxGridSize[]);
    printf("totalConstMem : %d.\n", prop.totalConstMem);
    printf("major.minor : %d.%d.\n", prop.major, prop.minor);
    printf("clockRate : %d.\n", prop.clockRate);
    printf("textureAlignment : %d.\n", prop.textureAlignment);
    printf("deviceOverlap : %d.\n", prop.deviceOverlap);
    printf("multiProcessorCount : %d.\n", prop.multiProcessorCount);
}

bool InitCUDA()
{
    //used to count the device numbers
    int count;    

    // get the cuda device count
    cudaGetDeviceCount(&count);
    if (count == ) {
        fprintf(stderr, "There is no device.\n");
        return false;
    }

    // find the device >= 1.X
    int i;
    for (i = ; i < count; ++i) {
        cudaDeviceProp prop;
        if (cudaGetDeviceProperties(&prop, i) == cudaSuccess) {
            if (prop.major >= ) {
                //printDeviceProp(prop);
                break;
            }
        }
    }

    // if can't find the device
    if (i == count) {
        fprintf(stderr, "There is no device supporting CUDA 1.x.\n");
        return false;
    }

    // set cuda device 
    cudaSetDevice(i);

    return true;
}
           

歡迎大家和我一起讨論和學習 GPU 程式設計。

[email protected]

http://blog.csdn.net/xsc_c