블록이 하나인 경우
: vector[threadIdx.x]
블록이 여러개 인 경우
: vector[blockIdx.x * blockDim.x + threadIdx.x]
1.2에서 계산한 vector의 index와 더불어, 담당할 데이터가 없는 스레드들은 연산에 참여하지 않도록 하는 예외처리를 해주는 코드를 포함하는 커널을 작성해보자.
__global__ void vecAdd(int* _a, int* _b, int* _c, int _size)
{
int tID = blockIdx.x * blockDim.x + threadIdx.x;
if(tID < _size) // 예외처리
_c[tID] = _a[tID] + _b[tID];
}
수정된 커널에 맞는 스레드 레이아웃 및 커널 호출
dim3 dimGrid(ceil((float)NUM_DATA / 256), 1, 1)
dim3 dimBlock(256, 1, 1)
vecAdd <<<dimGrid, dimBlock>>> (d_a, d_b, d_c, NUM_DATA)
: 하나의 블록 안에서 스레드의 전역 ID를 계산
1차원 블록
: 스레드의 전역 번호는 threadIdx.x
2차원 블록
: 2D_BLOCK_TID = (blockDim.x
* threadIdx.y
+ threadIdx.x
)
: 그림으로 설명하면 다음과 같다.
3차원 블록
: TID_IN_BLOCK = blockDim.x
x blockDim.y
x threadIdx.z
+ 2D_BLOCK_TID
: 그림으로 설명하면 다음과 같다.
: 만약 그리드 내 블록이 하나라면 TID_IN_BLOCK이 그리드 내에서 각 스레드의 전역 번호.
: 하지만 만약 블록이 여러 개라면, 다음의 정보가 필요하다.
1) 자신이 속한 블록의 앞 블록까지의 스레드 개수
2) 자신이 속한 블록 내에서 자신이 몇 번째 스레드인지 = TID_IN_BLOCK
1차원 그리드
: 자신이 속한 블록의 앞 블록까지의 스레드 개수 = blockIdx.x
x NUM_THREAD_IN_BLCOK
: 자신이 속한 블록 내에서 자신이 몇 번째 스레드인지 = TID_IN_BLCOK
: 1D_GRID_TIM = ( blcokIdx.x
x NUM_THREAD_IN_BLOCK ) + TID_IN_BLOCK
2차원 그리드
: 2D_GRID_TID = ( gridDIm.x
x blockDIm.y
) x NUM_THREAD_IN_BLOCK + 1D_GRID_TID
3차원 그리드
: GLOBAL_TID = ( gridDim.x
x gridDim.y
x blockDim.z
x NUM_THREAD_IN_BLOCK ) + 2D_GRID_TID
행렬 등 2차원 데이터에 대한 인덱싱 연습
: 2차원 스레드 번호를 사용해 각 스레드가 행렬의 담당 원소를 가리키게 하는 것이 대표적.
: 우선 하나의 블록만 사용하는 2차원 인덱싱 방법이다.
x차원 스레드 번호를 행렬의 열, y차원 스레드 번호를 행렬의 행에 대응.
: col = threadIdx.x
: row = threadIdx.y
행렬을 포함한 고차원 데이터도 메모리에는 1차원 형태로 저장
: index(row, col) = row x blockDim.x
+ col = threadIdx.y
x blockDim.x
+ threadIdx.x
다음은 예제코드이다.
__global__ void matAdd_2D_index (float* _dA, float* _dB, float* _dC)
{
unsigned int col = threadIdx.x;
unsigned int row = threadIdx.y;
unsigned in index = row * blockDim.x + col;
_dC[indxe] = _dA[index] + _dB[index];
}
// kernel call
dim3 blockDim(COL_SIZE, ROW_SIZE)
matAdd_2D_index <<<1, blockDim>>> (dA, dB, dC)
x차원 번호를 column, y차원 번호를 row로 매칭.
: col = ( blockDim.x
x blockIdx.x
) + threadIdx.x
: row = ( blockDim.y
x blockIdx.y
) + threadIdx.y
: 그림으로 설명하면 다음과 같다.
행렬이 메모리상에서는 1차원 배열 형태로 나열되는 것을 고려하면 행렬의 (row, col) 위치는 메모리상에서 다음과 같이 계산
: index (row, col) = row * COL_SIZE + col
커널 코드 작성
__global__ void MatAdd_G2D_B2D (float* matA, float* matB, float* matC, int ROW_SIZE, int COL_SIZE)
{
unsigned int col = threadIdx.x + blockIdx.x * blockDim.x;
unsigned int row = threadIdx.y + blockIdx.y * blockDim.y;
unsigned int index = row * COL_SIZE + col;
if (col < COL_SIZE && row < ROW_SIZE)
MatC[index] = MatA[index] + matB[index];
}
커널 호출
: 블록 크기를 (32, 32)로 잡고 커널을 호출해보자
dim3 blockDim(32, 32);
dim3 gird Dim(ceil((float)ROW_SIZE / blockDim.x), ceil((float)ROW_SIZE / blockDim.y));
MatAdd_G2D_B2D <<<girdDim, blockDim>>> (A, B, C, ROW_SIZE, COL_SIZE);
스레드 개수가 행렬 크기 (전체 원소 개수)와 같도록 블록과 그리드의 크기를 잡는다.
: x차원 전역 번호를 행렬의 열로 매칭하면 스레드 번호로는 행을 구분할 수 없다
: 이는 각 스레드가 본인 담당 열에 대한 모든 행을 처리하도록 하는 방법으로 해결.
: 그림으로 설명하면 다음과 같다.
커널 코드 작성
__global__ void MatAdd_G1D_B1D (float* matA, float* matB, float* matC, int ROW_SIZE, int COL_SIZE)
{
unsigned int col = threadIdx.x + blockIdx.x * blockDim.x;
if (col < COL_SIZE)
{
for (int row = 0; row < ROW_SIZE; row++) // 한 thread에서 모든 row에 대해 처리
{
int index = row * COL_SIZE + col;
MatC[index] = MatA[index] + MatB[index];
}
}
}
커널 호출
: 블록 크기를 (32)로 잡고 커널 호출
dim3 blockDim(32);
dim3 girdDim(ceil((float)COL_SIZE / blockDim.x));
MatAdd_G1D_B1D <<< gridDim, blockDim>>> (A, B, C, ROW_SIZE, COL_SIZE);
x차원 번호를 행렬의 열, y차원 번호를 행으로 매칭
: col = blockIdx.x
x blockDim.x
+ threadIdx.x
: row = blockIdx.y
: 그림으로 설명하면 다음과 같다.
커널 코드 작성
__global__ void MatAdd_G2D_B1D (float* matA, float* matB, float* matC, int ROW_SIZE, int COL_SIZE)
{
unsigned int col = threadIdx.x + blockIdx.x * blockDim.x;
unsigned int row = blckIdx.y;
unsigned int index = row * COL_SIZE + col;
if (col < COL_SIZE && row < ROW_SIZE)
MatC[index] = MatA[index] + MatB[index];
}
커널 호출
: 블록 크기를 (32)로 잡고 커널 호출
dim3 blockDim(32)
dim3 gridDim(ceil((float)COL_SIZE / blockDIm.x), ROW_SIZE);
MatAdd_G2D_B1D <<<gridDim, blockDim>>> (A, B, C, ROW_SIZE, COL_SIZE);