홈으로 돌아가기

만델브로트 SIMD 최적화 AVX2 OpenMP CUDA

이 글은 만델브로 집합 계산의 단계별 최적화를 상세히 설명합니다: 스칼라 C++ (7 FPS)에서 AVX2 SIMD (45 FPS), OpenMP (48 FPS), CUDA (520+ FPS)로. 전체 인트린식 코드, 멀티스레딩 및 GPU 구현과 AMD Ryzen 5 5600H, RTX 3050 벤치마크.

7에서 520 FPS로: 만델브로트 SIMD+CUDA 벡터화
Advertisement 728x90

# 만델브로 집합 계산 최적화: 스칼라 코드에서 SIMD, OpenMP, CUDA로

만델브로 집합은 복소평면의 각 점에 대해 독립적으로 계산됩니다: z_0 = 0에서 시작하여 z_{n+1} = z_n² + c 수열이 유계로 유지되는지 확인합니다. |z_n| > 2이면 수열이 발산합니다. 반복 횟수를 256으로 제한하여 계산이 종료되도록 합니다. 이 작업은 완벽하게 병렬화가 가능합니다—각 픽셀이 독립적으로 처리됩니다.

픽셀 색상은 |z|가 반경 2를 초과하는 반복 횟수에 따라 결정됩니다. 256단계 후에도 유계 내에 머무르는 점은 검은색으로 표시합니다.

스칼라 구현

기본 알고리즘은 C++에서 화면 좌표 [0, WIDTH] × [0, HEIGHT]를 복소평면 영역 [-1, 1] × [-1, 1]에 매핑합니다:

Google AdInline article slot
float c_y = -1.0f + screen_y * (2.0f / WINDOW_HEIGHT);
float c_x = -1.0f + screen_x * (2.0f / WINDOW_WIDTH);

반복 루프는 z_x²와 z_y²를 별도로 저장하여 최적화되었습니다:

while (z_x2 + z_y2 < MAX_RADIUS_2 && iterations < MAX_ITERATION_DEPTH) {
    z_y = 2 * z_x * z_y + c_y;
    z_x = z_x2 - z_y2 + c_x;
    z_x2 = z_x * z_x;
    z_y2 = z_y * z_y;
    iterations++;
}

AMD Ryzen 5 5600H에서 1920×1080 해상도, 256 반복 시 -O2 옵션으로 컴파일된 스칼라 코드는 7.0 ± 0.1 FPS (GCC/Clang)를 달성합니다.

AVX2 벡터화

256비트 ymm 레지스터에서의 SIMD는 명령어당 8개의 float를 처리합니다. <x86intrin.h>의 인트린직은 벡터화에 대한 명시적 제어를 제공합니다.

Google AdInline article slot

주요 벡터:

  • _c_x: 기본 좌표 + 오프셋 [0..7] × c_step_x
  • _z_x, _z_y, _z_x2, _z_y2, _z_xy: 8개 픽셀 상태
  • _iterations: 반복 카운터 (int32)

오프셋 초기화:

const __m256 _01234567 = _mm256_set_ps(7.0f, 6.0f, 5.0f, 4.0f, 3.0f, 2.0f, 1.0f, 0.0f);
_c_x = _mm256_add_ps(_c_x, _mm256_mul_ps(_c_step_x, _01234567));

반복 루프에서:

Google AdInline article slot
  • 반경 계산: _radius2 = _mm256_add_ps(_z_x2, _z_y2)
  • 지속 마스크: _mm256_cmp_ps(_radius2, _max_radius2, _CMP_LT_OS) → 모두 -1.0 또는 0.0
  • 활성 마스크 하에서 상태 업데이트
  • 8개 픽셀 처리 후 X 방향으로 이동: _c_x = _mm256_add_ps(_c_x, _8_c_steps_x)

마스크를 통해 8개 픽셀이 모두 탈출하면 동시에 루프를 종료할 수 있습니다.

OpenMP 멀티스레딩

OpenMP는 #pragma omp parallel for 지시어를 사용하여 행을 스레드에 분배합니다. 각 스레드는 자신의 픽셀 스트립을 독립적으로 계산합니다.

#pragma omp parallel for schedule(dynamic)

schedule(dynamic)은 부하 균형을 위해 사용되며—프랙탈 영역에 따라 반복 횟수가 다릅니다.

AVX2 + OpenMP를 6코어 Ryzen 5 5600H에서 결합하면 최대 40-50 FPS를 달성합니다.

CUDA를 사용한 GPU 가속

CUDA로 포팅하는 것은 간단합니다: 각 스레드가 하나의 픽셀을 계산합니다. CUDA 커널:

__global__ void mandelbrot_cuda(float* output, int width, int height) {
    int x = blockIdx.x * blockDim.x + threadIdx.x;
    int y = blockIdx.y * blockDim.y + threadIdx.y;
    if (x >= width || y >= height) return;
    
    float c_x = -2.0f + (float)x * scale_x;
    float c_y = -1.5f + (float)y * scale_y;
    
    float z_x = 0.0f, z_y = 0.0f;
    int iter = 0;
    
    while (z_x*z_x + z_y*z_y < 4.0f && iter < 256) {
        float temp = z_x*z_x - z_y*z_y + c_x;
        z_y = 2.0f * z_x * z_y + c_y;
        z_x = temp;
        iter++;
    }
    output[y*width + x] = (float)iter;
}

실행: dim3 block(16,16); dim3 grid((width+15)/16, (height+15)/16); mandelbrot_cuda<<<grid,block>>>(...)

노트북 GPU (RTX 3050)에서 Full HD 해상도로 500+ FPS를 달성합니다.

| 구현 | FPS (Ryzen 5 5600H) |

|-----------------|----------------------|

| Scalar | 7.0 |

| AVX2 | 45.2 |

| AVX2+OpenMP | 48.1 |

| CUDA | 520+ |

주요 교훈

  • 만델브로 집합은 SIMD, OpenMP, CUDA를 테스트하기 위한 병렬화가 매우 쉬운 벤치마크 작업입니다
  • AVX2는 루프당 8개 픽셀을 처리하여 스칼라 코드 대비 ~6.5배 속도 향상을 제공합니다
  • OpenMP는 6코어에서 동기화 오버헤드로 인해 6-7% 추가 향상을 줍니다
  • CUDA는 수천 개의 병렬 스레드로 인해 orders-of-magnitude 속도 향상을 제공합니다
  • 성능 측정은 렌더링을 제외합니다 (hyperfine, N회 실행 ~5s)

— Editorial Team

Advertisement 728x90

다음 읽기