CUDA學習日誌:入門例程和編程接口

接觸CUDA的時間並不長,最開始是在cuda-convnet的代碼中接觸CUDA代碼,當時確實看的比較痛苦。最近得空,在圖書館借了本《GPU高性能編程 CUDA實戰》來看看,同時也整理一些博客來加強學習效果。

Jeremy Lin

上篇博文我們主要是介紹了CUDA開發環境的配置和一些學習資源。現在我們正式進入CUDA的學習。如果你還記得上篇最後有一個“Hello World”的例子,你會發現它和C程序根本沒什麼差。不過,從這個Hello World我們來引出CUDA編程的一個重要區別:我們將CPU以及系統的內存稱爲主機(host),而將GPU及其內存稱爲設備(device)。而上篇的Hello World和我們以前寫過的代碼沒啥差別的原因在於它並不考慮主機之外的任何計算設備。


現在我們來看看如何使用設備(device)來執行代碼。在GPU設備上執行的函數,我們通常稱爲核函數(kernel)。

首先,Show the Code

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

const int N = 10;

__global__ void add_Jeremy(int*a, int*b, int*c)
{
	int tid = blockIdx.x;
	if (tid < N)
	{
		c[tid] = a[tid] + b[tid];
	}
}

int main()
{
	int a[N], b[N], c[N];
	int *dev_a, *dev_b, *dev_c;

	cudaMalloc((void**)&dev_a, N*sizeof(int));
	cudaMalloc((void**)&dev_b, N*sizeof(int));
	cudaMalloc((void**)&dev_c, N*sizeof(int));

	for (int i = 0; i < N; i++)
	{
		a[i] = -i;
		b[i] = i * i;
	}

	cudaMemcpy(dev_a, a, N*sizeof(int), cudaMemcpyHostToDevice);
	cudaMemcpy(dev_b, b, N*sizeof(int), cudaMemcpyHostToDevice);

	add_Jeremy<<<N,1>>>(dev_a, dev_b, dev_c);

	cudaMemcpy(c, dev_c, N*sizeof(int), cudaMemcpyDeviceToHost);

	for (int i = 0; i < N; i++)
	{
		printf("%d + %d = %d\n", a[i], b[i], c[i]);
	}

	cudaFree(dev_a);
	cudaFree(dev_b);
	cudaFree(dev_c);

	return 0;
}


上面的例子完成的功能是兩個向量的相加,夠簡單~,所以在邏輯上,我們基本不用多說什麼。

現在我們關注的是,CUDA的代碼與一般的C/C++程序的差別到底在哪裏?看上面的code,我想你應該可以看到如下幾點差別:


(1)函數類型限定符 __global__

這個限定符告訴編譯器,函數應該編譯爲在設備而不是主機運行,即函數add_Jeremy()將被交給編譯設備代碼的編譯器,而main函數將被交給主機編譯器。


關於函數類型限定符的完整介紹如下:

__device__

使用__device__限定符聲明的函數具有以下特徵:

  • 在設備執行;
  • 僅可通過設備調用。

__global__

使用__global__限定符可將函數聲明爲內核。此類函數:

  • 在設備上執行;
  • 僅可通過主機調用。

__host__

使用__host__ 限定符聲明的函數具有以下特徵:

  • 在主機上執行;
  • 僅可通過主機調用。

僅使用 _host_ 限定符聲明函數等同於不使用 _host_、_device_ 或 _global_ 限定符聲明函數,這兩種情況下,函數都將僅爲主機進行編譯。
但 _host_ 限定符也可與 _device_ 限定符一起使用,此時函數將爲主機和設備進行編譯。


一些限制:

  1. _device_ 和 _global_ 函數不支持遞歸。
  2. _device_ 和 _global_ 函數的函數體內無法聲明靜態變量。
  3. _device_ 和 _global_ 函數不得有數量可變的參數。
  4. _device_ 函數的地址無法獲取,但支持 _global_ 函數的函數指針。
  5. _global_ 和 _host_ 限定符無法一起使用。
  6. _global_ 函數的返回類型必須爲空。
  7. _global_ 函數的調用是異步的,也就是說它會在設備執行完成之前返回。
  8. _global_ 函數參數將同時通過共享存儲器傳遞給設備,且限制爲 256 字節。


(2)內建變量  blockIdx

內建變量blockIdx是一個包含三個元素x,y,z的結構體,分別表示當前線程所在塊在網格中x,y,z三個方向上的索引。


關於內建變量的完整介紹如下:

gridDim

此變量的類型爲dim3(dim3是一種整形向量類型,在定義類型爲dim3的變量時,未指定的任何組件都被初始化爲1),包含網格的維數。簡單地講,gridDim是一個包含三個元素x,y,z的結構體,分別表示網格在x,y,z三個方向上的尺寸,雖然其有三維,但是目前只能使用二維。

blockDim

此變量的類型也是dim3,是一個包含三個元素x,y,z的結構體,分別表示塊在x,y,z三個方向上的尺寸,對應於執行配置中的第一個參數,對應於執行配置的第二個參數。

threadIdx

此變量的類型是uint3,是一個包含三個元素x,y,z的結構體,分別表示當前線程在其所在塊中x,y,z三個方向上的索引。

warpSize

warpSzie表明warp的尺寸,在計算能力爲1.0的設備中,這個值是24,在1.0以上的設備中,這個值是32.


(3)內置函數


cudaMalloc((void**)&dev_Ptr, size_t size)

code中的cudaMalloc()是用來分配內存的,這個函數調用的行爲非常類似於標準的C函數malloc(),但該函數的作用是告訴CUDA運行時在設備上分配內存。第一個參數是一個指針,指向用於保存新分配內存地址的變量,第二個參數是分配內存的大小。除了分配內存的指針不是作爲函數的返回值外,這個函數的行爲與malloc()是相同的,並且返回類型爲void*。

一些要求:

  • 可以將cudaMalloc()分配的指針傳遞給在設備上執行的函數;
  • 可以在設備代碼中使用cudaMalloc()分配的指針進行內存讀/寫操作;
  • 可以將cudaMalloc()分配的指針傳遞給在主機上執行的函數;
  • 不能在主機代碼中使用cudaMalloc()分配的指針進行內存讀/寫操作。

cudaFree(void* dev_Ptr)

code中的cudaFree()用來釋放cudaMalloc()分配的內存,這個函數的行爲和free()的行爲非常類似。


cudaMemcpy(void* dst, const void* src, size_t size, cudaMemcpyKind kind)

從上面我們可以知道,主機指針只能訪問主機代碼中的內存,而設備指針只能訪問設備代碼中的內存。因此,如果想要實現互相訪問,則必須通過cudaMemcpy()函數來實現。這個函數的調用行爲類似於標準C中的memcpy(),只不過多了一個參數來指定設備內存指針究竟是源指針還是目標指針。

在上面的code中cudaMemcpy()的最後一個參數爲cudaMemcpyDeviceToHost,這個參數將告訴運行時源指針是一個設備指針,而目標指針是一個主機指針。

cudaMemcpyHostToDevice將告訴運行時源指針位於主機上,而目標指針是位於設備上的。


(4)核函數add_Jeremy<<<N,1>>>()


這裏面主要的差別是尖括號和裏面的兩個值。這個看上去有點奇怪的函數調用實際上表示調用設備代碼。尖括號表示要將一些參數傳遞給運行時系統。這些參數並不是傳遞給設備代碼的參數,而是告訴運行時如何啓動設備代碼。傳遞給設備代碼本身的參數是放在圓括號中傳遞的,就像標準的函數調用一樣。

尖括號裏面:

  • 第一個參數表示設備在執行核函數時使用的並行線程塊的數量。在這個實例中,指定這個參數爲N;
  • 第二個參數表示CUDA運行時在每個線程塊中創建的線程數量。

在這個核函數中總共啓動的線程數量如下:

N個線程塊 * 1個線程/線程塊 = N個並行線程

事實上,我們也可以啓動N/2個線程塊,每個線程塊包含2個線程,或者N/4個線程塊,每個線程塊包含4個線程,以此類推。

在上文的code中,當啓動核函數add_Jeremy()時,我們將並行線程塊的數量指定爲N。這個並行線程塊集合也稱爲一個線程格(Grid)。這是告訴運行時系統,我們想要一個一維的線程格,其中包含N個線程塊。每個線程塊的blockIdx.x值都是不同的,第一個blockIdx.x爲0,最後一個線程塊的blockIdx.x爲N-1,並且所有線程塊都運行相同的設備代碼。運行時,將用相應的線程索引來替換掉blockIdx.x。


本文地址:http://blog.csdn.net/linj_m/article/details/41345263

更多資源請 關注博客:LinJM-機器視覺  微博:林建民-機器視覺


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