SYCL 입문 시리즈의 여섯 번째 글입니다. 지금까지 SYCL 기초 개념(메모리 모델, 커널 작성), 다양한 디바이스(NVIDIA, Qualcomm GPU) 지원 전략, ND-Range와 로컬 메모리, USM 등 최적화 개념까지 살펴보았습니다. 이번 글에서는 이제까지 익힌 내용을 기반으로 간단한 벤치마크를 수행하고, 성능 차이를 관찰하는 방법을 소개합니다. 또한, 디버깅/프로파일링 도구(RenderDoc, Nsight Graphics, Intel VTune 등)를 간단히 연계해, SYCL 코드의 성능 분석 기초를 다뤄보겠습니다.
목표
- 이전 글에서 만든 매트릭스 곱(또는 벡터 연산) 예제를 반복 실행하며 시간 측정
- ND-Range 워크그룹 크기 변화, 로컬 메모리 사용 여부에 따라 성능 차이 관찰
- 간단한 타이머(C++ chrono)로 GPU 커널 실행 시간 측정
- 디버깅/프로파일링 툴과의 연계를 통해 성능 병목 식별 기초 소개
성능 측정 전략
SYCL 코드를 측정하는 가장 간단한 방법은 std::chrono
를 사용해 커널 전후 시간을 측정하는 것입니다. 단, GPU 커널 실행이 비동기적으로 진행되므로, q.wait()
로 커널 완료를 보장한 뒤 시간 측정해야 합니다.
예:
auto start = std::chrono::high_resolution_clock::now();
// 커널 실행
q.submit(...);
q.wait(); // 커널 완료 대기
auto end = std::chrono::high_resolution_clock::now();
double elapsed = std::chrono::duration<double, std::milli>(end-start).count();
std::cout << "Kernel execution time: " << elapsed << " ms\n";
반복 실행하며 워크그룹 크기, ND-Range 형태를 바꿔보면 성능 차이를 관찰할 수 있습니다.
로컬 액세서 사용 전후 성능 비교
지난 글(#5)에서 매트릭스 곱 예제를 작성했다고 가정합시다. 로컬 메모리를 사용하지 않는 단순 버전과 로컬 메모리를 사용한 최적화 버전 두 가지 코드를 준비합니다. 둘 다 M=1024, K=1024, N=1024
정도의 크기로 매트릭스 곱을 수행하고, 시간 측정 결과를 비교할 수 있습니다.
// 의사 코드 (로컬 메모리 버전)
// 로컬 액세서 활용해 타일링하면 전역 메모리 접근 최소화
// 로컬 메모리 타일 크기: 16x16
// 두 커널(MxN 연산 동일) 실행 시간 비교
실제 코드를 빌드할 때 -D USE_LOCAL_MEM=ON
같은 CMake 옵션을 통해 로컬 메모리 버전 커널을 활성화한 뒤, 두 번 빌드해 결과를 비교하는 방식도 가능.
백엔드 전환 시 성능 차이
앞서 배우듯, cmake -D BACKEND=cuda ..
로 NVIDIA GPU 백엔드를 사용하거나, cmake -D BACKEND=opencl ..
로 Qualcomm GPU 백엔드를 사용해 동일 커널을 실행할 수 있습니다. 이렇게 하면 벤더별 디바이스에서 같은 커널이 얼마나 다른 성능을 보이는지 관찰할 수 있습니다.
- NVIDIA GPU에서 CUDA 백엔드: 보통 뛰어난 컴퓨팅 성능 발휘
- Qualcomm GPU(OpenCL 백엔드): 임베디드나 모바일 GPU인 경우, 성능 특성 다를 수 있음
이러한 실험을 통해 SYCL 코드 이식성을 직접 체감할 수 있습니다.
디버깅/프로파일링 도구 연계
SYCL 코드 성능 분석을 위해 다음과 같은 도구를 고려할 수 있습니다.
- RenderDoc: 그래픽 디버거지만, 일부 수준에서 컴퓨트 커널 호출 패턴 관찰 가능
- Nsight Graphics / Nsight Systems(NVIDIA): CUDA 백엔드 사용 시, Nsight Systems로 CPU/GPU 타임라인, 커널 실행 시간 관찰 가능
- Intel VTune Profiler: oneAPI DPC++ 사용 시 Intel GPU 및 CPU 성능 분석에 유용
- AMD Radeon GPU Profiler(RGP): AMD GPU 백엔드가 가능하다면 적용 가능 (이 시리즈에서는 언급만)
처음 입문자는 이러한 툴 사용이 복잡할 수 있지만, 다음과 같이 기본적인 접근을 해볼 수 있습니다.
- Nsight Systems 예: CUDA 백엔드로 빌드한 SYCL 바이너리를 Nsight Systems로 프로파일링하면, GPU 커널의 실행 시점과 CPU 함수 호출 시점을 타임라인으로 확인할 수 있습니다. 이를 통해 워크그룹 크기 변경 후 커널 실행 시간이 어떻게 변하는지 직관적으로 파악 가능.
- VTune Profiler 예: oneAPI DPC++를 사용 중이라면 VTune으로 CPU/GPU offload 타임라인을 살펴보고, 메모리 전송 패턴, 커널 실행 시간 등을 분석해 어느 부분을 최적화할지 결정할 수 있습니다.
이 과정에서 Modern C++ 스타일 SYCL 코드(버퍼/액세서, RAII, 예외 처리)는 코드 구조를 명확히 하여, 프로파일링 결과에서 어떤 커널이 어떤 코드 부분에 해당하는지 쉽게 매칭할 수 있습니다.
예제: 워크그룹 크기 변경으로 성능 비교
간단히, cmake -D WORKGROUP_SIZE=8
과 cmake -D WORKGROUP_SIZE=16
등을 통해 빌드 옵션을 바꿔 다른 워크그룹 크기로 커널을 실행해봅시다.
-D WORKGROUP_SIZE=8
시: 커널 실행 후 시간 출력-D WORKGROUP_SIZE=16
시: 커널 실행 후 시간 출력
시간 차이를 기록하고, 어느 경우가 더 빠른지 관찰할 수 있습니다.
이를 통해 기본적인 성능 최적화 감각을 키울 수 있습니다.
정리 및 다음 글 예고
이번 글에서는 SYCL 코드 성능 비교, 간단한 벤치마크, 디버깅/프로파일링 툴과의 연계를 통해 성능 분석 기초를 살펴봤습니다. 이제 SYCL 코드가 단순히 이식성뿐 아니라, 성능 측면에서도 다양한 시도를 통해 최적화할 수 있음을 이해했을 것입니다.
다음 글(#7)에서는 지금까지 배운 모든 내용을 종합적으로 정리하고, 앞으로 더 고급 주제로 나아갈 때 고려할 수 있는 라이브러리, 예를 들어 DPC++ 라이브러리나 SYCL BLAS(선형대수 라이브러리) 활용, 그리고 Conda나 Windows 환경에서의 확장 방안 등을 제안할 예정입니다.
'개발 이야기 > SYCL (시클)' 카테고리의 다른 글
[SYCL 입문 #7] 종합 정리 및 다음 단계로의 길잡이 (0) | 2024.12.20 |
---|---|
[SYCL 입문 #5] 매트릭스 곱 실용 예제와 성능 개선 아이디어 (0) | 2024.12.20 |
[SYCL 입문 #4] ND-Range 활용과 메모리 최적화 기법 소개 (0) | 2024.12.19 |
[SYCL 입문 #3] 메모리 모델 & 커널 작성 패턴 이해하기 (0) | 2024.12.19 |
[SYCL 입문 #2] NVIDIA와 Qualcomm GPU에서 SYCL 코드 실행하기 (1) | 2024.12.19 |