Framework/CUDA
CUDA - 4장. CUDA 프로그래밍 기본
개발_노트
2025. 3. 28. 20:49
💻 4장. CUDA 프로그래밍 기본
🎯 학습 목표
이 장에서는 CUDA 프로그래밍의 핵심 실습 내용을 단계별로 익히며,
실제 코드 작성, 실행, 메모리 처리, 동기화, 성능 측정 및 기본 최적화 기법을 배웁니다.
4.1 CUDA 코드 작성 및 실행 방법
🧾 기본 코드 예제
#include <stdio.h>
__global__ void helloFromGPU() {
printf("Hello from GPU thread %d\n", threadIdx.x);
}
int main() {
helloFromGPU<<<1, 4>>>(); // 1블록, 4스레드
cudaDeviceSynchronize(); // GPU 연산 완료 대기
return 0;
}
⚙️ 빌드 및 실행
nvcc hello.cu -o hello
./hello
✅ 실행 결과
Hello from GPU thread 0
Hello from GPU thread 1
Hello from GPU thread 2
Hello from GPU thread 3
4.2 커널 함수 정의 및 실행
__global__ void square(float* out, float* in) {
int idx = threadIdx.x;
out[idx] = in[idx] * in[idx];
}
실행 예시
square<<<1, 256>>>(d_out, d_in); // 1개의 블록, 256개의 스레드
4.3 스레드 인덱싱 및 블록 인덱싱
기본 인덱싱
int idx = threadIdx.x + blockIdx.x * blockDim.x;
2차원 인덱싱 예시
int x = threadIdx.x + blockIdx.x * blockDim.x;
int y = threadIdx.y + blockIdx.y * blockDim.y;
변수 | 설명 |
threadIdx.x | 블록 내 스레드 번호 |
blockIdx.x | 그리드 내 블록 번호 |
blockDim.x | 블록 내 스레드 수 |
4.4 메모리 할당 및 데이터 전송
필수 함수
함수 | 설명 |
cudaMalloc() | GPU 메모리 할당 |
cudaMemcpy() | CPU ↔ GPU 간 데이터 복사 |
cudaFree() | GPU 메모리 해제 |
예제 코드
float *d_in, *d_out;
int size = N * sizeof(float);
cudaMalloc(&d_in, size);
cudaMemcpy(d_in, h_in, size, cudaMemcpyHostToDevice);
cudaMemcpy(h_out, d_out, size, cudaMemcpyDeviceToHost);
cudaFree(d_in); cudaFree(d_out);
📐 메모리 정렬 최적화 팁 (Memory Alignment)
성능 저하 원인
- 스레드들이 비연속적인 메모리 위치에 접근하면 coalescing이 되지 않아 성능 저하 발생
✅ 최적화 전략
전략 | 설명 |
연속 주소 접근 | 스레드들이 연속된 메모리 위치를 접근해야 함 |
벡터 타입 활용 | float4, int4 등 자연스럽게 정렬된 타입 사용 |
패딩 추가 | 구조체 내 멤버 간 정렬 보장 |
공유 메모리 bank conflict 방지 | 공유 메모리 접근 시 주소 정렬 필요 |
예시
struct __align__(16) Vec4 {
float x, y, z, w;
};
💡 메모리 접근 패턴이 정렬되어 있으면 전역 메모리 접근 속도 최대 10배 이상 차이 날 수 있습니다.
4.5 동기화 및 실행 시간 측정
동기화
cudaDeviceSynchronize(); // GPU 연산 완료 대기
실행 시간 측정 (CUDA Event API)
cudaEvent_t start, stop;
cudaEventCreate(&start);
cudaEventCreate(&stop);
cudaEventRecord(start);
myKernel<<<grid, block>>>();
cudaEventRecord(stop);
cudaEventSynchronize(stop);
float ms = 0;
cudaEventElapsedTime(&ms, start, stop);
printf("Execution Time: %.3f ms\n", ms);
🛡️ 에러 처리 매크로
#define CUDA_CHECK(call) { \
cudaError_t err = call; \
if (err != cudaSuccess) { \
fprintf(stderr, "CUDA Error: %s\n", cudaGetErrorString(err)); \
exit(EXIT_FAILURE); \
} \
}
💡 모든 cudaMalloc, cudaMemcpy, 커널 실행 후에는 반드시 에러 체크 필요
✅ 종합 예제: 벡터 덧셈
__global__ void vectorAdd(float* a, float* b, float* c, int n) {
int idx = threadIdx.x + blockIdx.x * blockDim.x;
if (idx < n) c[idx] = a[idx] + b[idx];
}
int main() {
int N = 512;
size_t size = N * sizeof(float);
float *h_a = (float*)malloc(size);
float *h_b = (float*)malloc(size);
float *h_c = (float*)malloc(size);
for (int i = 0; i < N; i++) {
h_a[i] = i;
h_b[i] = i * 2;
}
float *d_a, *d_b, *d_c;
CUDA_CHECK(cudaMalloc(&d_a, size));
CUDA_CHECK(cudaMalloc(&d_b, size));
CUDA_CHECK(cudaMalloc(&d_c, size));
CUDA_CHECK(cudaMemcpy(d_a, h_a, size, cudaMemcpyHostToDevice));
CUDA_CHECK(cudaMemcpy(d_b, h_b, size, cudaMemcpyHostToDevice));
int threads = 256;
int blocks = (N + threads - 1) / threads;
vectorAdd<<<blocks, threads>>>(d_a, d_b, d_c, N);
CUDA_CHECK(cudaGetLastError());
CUDA_CHECK(cudaMemcpy(h_c, d_c, size, cudaMemcpyDeviceToHost));
printf("h_c[0] = %.1f\n", h_c[0]); // 결과: 0.0
cudaFree(d_a); cudaFree(d_b); cudaFree(d_c);
free(h_a); free(h_b); free(h_c);
}
🧯 디버깅 팁
도구 | 설명 |
cudaGetLastError() | 커널 실행 오류 확인 |
cuda-memcheck | 메모리 접근 오류 탐지 |
printf() | 커널 내부 디버깅용 |
NVIDIA Nsight | 성능 분석 및 디버깅 지원 GUI 도구 |
🌐 실습 환경 안내
환경 | 특징 |
로컬 GPU 환경 | 가장 안정적이고 권장 |
Google Colab (Pro) | 제한된 GPU 환경에서 실습 가능 |
Docker + NVIDIA 컨테이너 | 독립된 CUDA 개발 환경 구성 가능 |
Visual Studio + Nsight | Windows 기반 개발/디버깅 최적화 IDE |
📌 최종 요약표
항목 | 내용 |
작성 및 실행 | .cu 파일, nvcc 컴파일, <<<>>> 커널 실행 |
메모리 처리 | cudaMalloc, cudaMemcpy, cudaFree |
스레드 인덱싱 | threadIdx, blockIdx, blockDim 조합 |
성능 측정 | cudaEventElapsedTime() 사용 |
에러 처리 | CUDA_CHECK, cudaGetLastError() |
정렬 최적화 | 연속 접근, 벡터 타입, 구조체 정렬 고려 |
디버깅 | cuda-memcheck, Nsight, printf 등 활용 |