閱讀712 返回首頁    go 小米 go 小米6


CUDA從入門到精通(六):塊並行

同一版本的代碼用了這麼多次,有點過意不去,於是這次我要做較大的改動大笑,大家要擦亮眼睛,拭目以待。

 

塊並行相當於操作係統中多進程的情況,上節說到,CUDA有線程組(線程塊)的概念,將一組線程組織到一起,共同分配一部分資源,然後內部調度執行。線程塊與線程塊之間,毫無瓜葛。這有利於做更粗粒度的並行。我們將上一節的代碼改為塊並行版本如下:

 

#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>
cudaError_t addWithCuda(int *c, const int *a, const int *b, size_t size);
__global__ void addKernel(int *c, const int *a, const int *b)
{
    int i = blockIdx.x;
    c[i] = a[i] + b[i];
}
int main()
{
    const int arraySize = 5;
    const int a[arraySize] = { 1, 2, 3, 4, 5 };
    const int b[arraySize] = { 10, 20, 30, 40, 50 };
    int c[arraySize] = { 0 };
    // Add vectors in parallel.
    cudaError_t cudaStatus;
	int num = 0;
	cudaDeviceProp prop;
	cudaStatus = cudaGetDeviceCount(&num);
	for(int i = 0;i<num;i++)
	{
		cudaGetDeviceProperties(&prop,i);
	}
	cudaStatus = addWithCuda(c, a, b, arraySize);
    if (cudaStatus != cudaSuccess) 
	{
        fprintf(stderr, "addWithCuda failed!");
        return 1;
    }
    printf("{1,2,3,4,5} + {10,20,30,40,50} = {%d,%d,%d,%d,%d}\n",c[0],c[1],c[2],c[3],c[4]);
    // cudaThreadExit must be called before exiting in order for profiling and
    // tracing tools such as Nsight and Visual Profiler to show complete traces.
    cudaStatus = cudaThreadExit();
    if (cudaStatus != cudaSuccess) 
	{
        fprintf(stderr, "cudaThreadExit failed!");
        return 1;
    }
    return 0;
}
// Helper function for using CUDA to add vectors in parallel.
cudaError_t addWithCuda(int *c, const int *a, const int *b, size_t size)
{
    int *dev_a = 0;
    int *dev_b = 0;
    int *dev_c = 0;
    cudaError_t cudaStatus;

    // Choose which GPU to run on, change this on a multi-GPU system.
    cudaStatus = cudaSetDevice(0);
    if (cudaStatus != cudaSuccess) 
	{
        fprintf(stderr, "cudaSetDevice failed!  Do you have a CUDA-capable GPU installed?");
        goto Error;
    }
    // Allocate GPU buffers for three vectors (two input, one output)    .
    cudaStatus = cudaMalloc((void**)&dev_c, size * sizeof(int));
    if (cudaStatus != cudaSuccess) 
	{
        fprintf(stderr, "cudaMalloc failed!");
        goto Error;
    }
    cudaStatus = cudaMalloc((void**)&dev_a, size * sizeof(int));
    if (cudaStatus != cudaSuccess) 
	{
        fprintf(stderr, "cudaMalloc failed!");
        goto Error;
    }
    cudaStatus = cudaMalloc((void**)&dev_b, size * sizeof(int));
    if (cudaStatus != cudaSuccess) 
	{
        fprintf(stderr, "cudaMalloc failed!");
        goto Error;
    }
    // Copy input vectors from host memory to GPU buffers.
    cudaStatus = cudaMemcpy(dev_a, a, size * sizeof(int), cudaMemcpyHostToDevice);
    if (cudaStatus != cudaSuccess) 
	{
        fprintf(stderr, "cudaMemcpy failed!");
        goto Error;
    }
    cudaStatus = cudaMemcpy(dev_b, b, size * sizeof(int), cudaMemcpyHostToDevice);
    if (cudaStatus != cudaSuccess) 
	{
        fprintf(stderr, "cudaMemcpy failed!");
        goto Error;
    }
    // Launch a kernel on the GPU with one thread for each element.
    addKernel<<<size,1 >>>(dev_c, dev_a, dev_b);
    // cudaThreadSynchronize waits for the kernel to finish, and returns
    // any errors encountered during the launch.
    cudaStatus = cudaThreadSynchronize();
    if (cudaStatus != cudaSuccess) 
	{
        fprintf(stderr, "cudaThreadSynchronize returned error code %d after launching addKernel!\n", cudaStatus);
        goto Error;
    }
    // Copy output vector from GPU buffer to host memory.
    cudaStatus = cudaMemcpy(c, dev_c, size * sizeof(int), cudaMemcpyDeviceToHost);
    if (cudaStatus != cudaSuccess) 
	{
        fprintf(stderr, "cudaMemcpy failed!");
        goto Error;
    }
Error:
    cudaFree(dev_c);
    cudaFree(dev_a);
    cudaFree(dev_b);    
    return cudaStatus;
}


和上一節相比,隻有這兩行有改變,<<<>>>裏第一個參數改成了size,第二個改成了1,表示我們分配size個線程塊,每個線程塊僅包含1個線程,總共還是有5個線程。這5個線程相互獨立,執行核函數得到相應的結果,與上一節不同的是,每個線程獲取id的方式變為int i = blockIdx.x;這是線程塊ID。

 

於是有童鞋提問了,線程並行和塊並行的區別在哪裏?

線程並行是細粒度並行,調度效率高;塊並行是粗粒度並行,每次調度都要重新分配資源,有時資源隻有一份,那麼所有線程塊都隻能排成一隊,串行執行。

那是不是我們所有時候都應該用線程並行,盡可能不用塊並行?

當然不是,我們的任務有時可以采用分治法,將一個大問題分解為幾個小規模問題,將這些小規模問題分別用一個線程塊實現,線程塊內可以采用細粒度的線程並行,而塊之間為粗粒度並行,這樣可以充分利用硬件資源,降低線程並行的計算複雜度。適當分解,降低規模,在一些矩陣乘法、向量內積計算應用中可以得到充分的展示。

 

實際應用中,常常是二者的結合。線程塊、線程組織圖如下所示。

 

多個線程塊組織成了一個Grid,稱為線程格(經曆了從一位線程,二維線程塊到三維線程格的過程,立體感很強啊)。

 

好了,下一節我們介紹流並行,是更高層次的並行。

最後更新:2017-04-03 16:48:42

  上一篇:go hdu 4607 Park Visit dfs
  下一篇:go CUDA從入門到精通(五):線程並行