[CUDA & Modern C++로 GPU 프로그래밍 시작하기] #8: 디버깅과 성능 프로파일링 기초

지금까지 우리는 CUDA 프로그래밍 기초부터 시작해 Host-Device 메모리 관리, 스레드/블록/그리드 개념, 비동기 스트림, 메모리 계층(Shared/Constant) 최적화 기법, 그리고 CMake와 Modern C++를 활용한 빌드 환경 개선까지 쭉 달려왔습니다. 이제 어느 정도 기본기가 갖추어졌다면, 실제 GPU 코드가 의도대로 동작하고 있는지 확인하고, 혹시 병목현상이 일어나지는 않는지 점검하는 단계가 필요합니다.

 

이번 글에서는 디버깅(Debugging)성능 프로파일링(Performance Profiling) 방법을 간단히 살펴볼 겁니다. 한 번에 모든 툴과 기법을 다루긴 어렵지만, 대표적인 도구와 기본적인 절차를 익혀두면 앞으로 문제 해결에 큰 도움이 됩니다.

GPU 디버깅 기본 아이디어

CPU 디버깅과 달리 GPU 디버깅은 몇 가지 제약이 있습니다. GPU 코드 디버깅을 위해 일반적으로 다음과 같은 방법을 활용할 수 있습니다.

  1. printf 디버깅: 가장 단순한 방법입니다. GPU 커널 내에서 printf를 활용해 변수 값이나 인덱스를 출력할 수 있습니다. 다만 너무 많은 printf는 성능 저하를 야기하고, 출력이 뒤죽박죽 섞일 수 있으니 주의해야 합니다.
  2. CUDA-GDB: NVIDIA가 제공하는 CUDA용 GDB 디버거를 사용하면, CPU 디버거처럼 브레이크포인트를 걸고 변수를 확인할 수 있습니다.이런 식으로 커널에 브레이크포인트를 걸고 변수 상태를 살펴볼 수 있습니다.
  3. cuda-gdb ./my_app (cuda-gdb) break vector_add_kernel (cuda-gdb) run
  4. NVIDIA Nsight Systems / Nsight Compute: 단순 디버깅을 넘어 성능 분석을 지원하는 툴도 디버깅에 유용합니다. Nsight Compute로 커널 레벨에서 메모리 접근 패턴이나 스레드 동작을 심층 분석할 수 있습니다.

이 외에도 Visual Studio, Nsight Integration, Nsight Eclipse Edition 등을 통해 GUI 기반 디버깅 환경을 구축할 수도 있습니다.

성능 프로파일링: 어디에서 병목이 발생할까?

GPU 성능 최적화의 핵심은 병목을 찾아 제거하는 것입니다. 프로파일링 도구를 사용하면 GPU 코드의 실행 시간, 메모리 대역폭 활용도, 스레드 활용도 등을 직관적으로 확인할 수 있습니다.

NVIDIA Nsight Systems

Nsight Systems는 애플리케이션 전체를 시각화해, CPU와 GPU 사이의 상호 작용, API 호출 시간, GPU 커널 실행 시간 등을 보여주는 종합적인 프로파일링 도구입니다.

  • CPU-GPU 오버랩(Overlap) 정도 파악
  • 특정 커널이 전체 실행 시간에서 차지하는 비율 확인
  • 비동기 스트림 병렬화 정도 점검

사용 예:

nsys profile -o profile_report ./my_app

이렇게 실행하면 profile_report.qdrep 파일이 생성되고, Nsight Systems GUI로 로드해 타임라인을 분석할 수 있습니다.

NVIDIA Nsight Compute

Nsight Compute는 특정 GPU 커널의 성능 특성을 깊이 있게 분석하는 툴입니다. 메모리 접근 패턴, 워프(divergence) 현상, L1/L2 캐시 히트율, SM 활용도 등 다양한 메트릭을 제공합니다.

사용 예:

ncu ./my_app

이렇게 하면 실행 시 해당 커널들의 상세 메트릭을 확인할 수 있는 보고서를 생성합니다. 여기서 어떤 메모리 계층을 더 활용해야 하는지, 스레드 다이버전스(Thread divergence)를 줄이기 위해 코드 구조를 어떻게 개선해야 하는지 등을 알 수 있습니다.

디버깅 & 프로파일링 순서 예시

  1. 기능 점검: 우선 printf나 CUDA-GDB를 활용해 기본 기능이 의도대로 동작하는지 확인합니다.
  2. 기초 프로파일링: Nsight Systems로 전체 프로그램 흐름을 파악해 GPU 커널 실행 시간이 긴 부분, CPU-GPU 동기화 대기 시간 등을 확인합니다.
  3. 정밀 분석: Nsight Compute를 사용해 문제 커널을 자세히 살펴봅니다. 메모리 대역폭 병목인지, 워프 다이버전스 문제인지, 캐시 활용이 부족한지 등의 단서를 얻습니다.
  4. 개선과 재측정: Shared Memory 최적화, Block/Thread 구조 변경, 비동기 스트림 재배치 등의 개선을 적용한 뒤, 다시 프로파일링해 성능 향상 여부를 확인합니다.

Before vs After: 감으로 해결 vs 근거 기반 최적화

Before:

  • 코드가 느리면 어디서 느려지는지 감으로 추측
  • CPU 디버깅 경험만으로 GPU 코드에 접근하다가 혼란

After:

  • Nsight 도구와 CUDA-GDB 같은 툴을 활용해 근거 기반의 디버깅·최적화
  • 병목을 정량적으로 파악하고, 개선 전후를 비교

이 접근 방식을 통해 엔지니어링 팀은 프로젝트 규모가 커지더라도 일관된 성능 관리와 유지보수가 가능합니다.

다음 글 예고

다음 글(#9)에서는 지금까지 배운 내용을 간단한 실전 예제를 통해 종합 정리해보겠습니다. 간단한 이미지 처리나 벡터 연산을 현대적 C++와 CUDA, CMake, 디버깅 및 프로파일링을 결합해 실제로 작동하는 프로젝트로 구현해봅시다.

유용한 링크 & 리소스

반응형