PART 8 - 강의 3/6

ParallelFor

대량 데이터의 병렬 처리로 CPU 코어 최대 활용

01

ParallelFor란?

for 루프의 병렬화

ParallelFor는 반복 작업을 여러 스레드에 자동으로 분배합니다. 각 반복이 독립적이라면 선형적 성능 향상을 기대할 수 있습니다.

일반 For Loop

0
1
2
3
4
5
6
7

단일 스레드에서 순차 실행

ParallelFor (4 스레드)

0
1
Thread 1
2
3
Thread 2
4
5
Thread 3
6
7
Thread 4

4배 빠른 처리 (이론상)

02

기본 사용법

ParallelFor 구문

C++
#include "Async/ParallelFor.h" // 기본 ParallelFor TArray<FVector> Positions; TArray<float> Results; Results.SetNum(Positions.Num()); ParallelFor(Positions.Num(), [&](int32 Index) { // 각 반복이 별도 스레드에서 실행될 수 있음 Results[Index] = ExpensiveCalculation(Positions[Index]); }); // 완료 후 Results 사용 가능 // ParallelFor는 모든 작업 완료까지 블로킹
핵심 규칙

각 반복은 독립적이어야 합니다. 반복 간에 공유 상태를 수정하면 Race Condition이 발생합니다. 결과는 인덱스별로 분리된 배열에 저장하세요.

03

옵션과 플래그

ParallelFor 세부 조정

C++
// 청크 크기 지정 // 작은 작업은 청크로 묶어서 오버헤드 감소 ParallelFor(Positions.Num(), [&](int32 Index) { Results[Index] = Calculate(Positions[Index]); }, 100); // 청크 크기 100 // EParallelForFlags 사용 ParallelFor(Positions.Num(), [&](int32 Index) { Results[Index] = Calculate(Positions[Index]); }, EParallelForFlags::None); // 플래그 옵션: // - None: 기본값 // - ForceSingleThread: 디버깅용 단일 스레드 실행 // - Unbalanced: 불균등 작업 부하용 // - BackgroundPriority: 낮은 우선순위
플래그 용도 사용 시나리오
None 기본 병렬 실행 대부분의 경우
ForceSingleThread 단일 스레드 강제 디버깅, Race Condition 확인
Unbalanced 동적 작업 분배 반복별 작업량이 다를 때
BackgroundPriority 낮은 우선순위 메인 로직 방해 최소화
04

실전 예제

게임에서의 활용

거리 계산 병렬화

C++
// 플레이어와 모든 적의 거리 계산 void AMyGameMode::UpdateEnemyDistances() { FVector PlayerLocation = Player->GetActorLocation(); // 결과 배열 준비 TArray<float> Distances; Distances.SetNum(Enemies.Num()); // 병렬 거리 계산 ParallelFor(Enemies.Num(), [&](int32 Index) { if (Enemies[Index]) { Distances[Index] = FVector::Dist( PlayerLocation, Enemies[Index]->GetActorLocation()); } }); // 결과 처리 (Game Thread에서) for (int32 i = 0; i < Enemies.Num(); i++) { if (Distances[i] < ActivationDistance) { Enemies[i]->Activate(); } } }

메시 데이터 처리

C++
// 대량 버텍스 변환 void TransformVertices(TArray<FVector>& Vertices, const FTransform& Transform) { ParallelFor(Vertices.Num(), [&](int32 Index) { Vertices[Index] = Transform.TransformPosition(Vertices[Index]); }); } // 노멀 재계산 void RecalculateNormals( const TArray<FVector>& Vertices, const TArray<int32>& Indices, TArray<FVector>& Normals) { Normals.SetNum(Vertices.Num()); // 삼각형 단위로 병렬 처리 int32 TriangleCount = Indices.Num() / 3; ParallelFor(TriangleCount, [&](int32 TriIndex) { int32 i0 = Indices[TriIndex * 3]; int32 i1 = Indices[TriIndex * 3 + 1]; int32 i2 = Indices[TriIndex * 3 + 2]; FVector Normal = FVector::CrossProduct( Vertices[i1] - Vertices[i0], Vertices[i2] - Vertices[i0]).GetSafeNormal(); // 주의: 같은 버텍스에 여러 삼각형이 쓸 수 있음 // Atomic 연산 또는 별도 처리 필요 }); }
05

성능 고려사항

언제 사용하고 언제 피해야 하는가

1000+

반복 수

권장 최소 반복 수
~10us

반복당 시간

병렬화 이점 발생
~50us

오버헤드

스레드 분배 비용
사용 지침
  • 적합한 경우 - 대량 데이터(1000+), 무거운 계산, 독립적 반복
  • 부적합한 경우 - 적은 반복, 가벼운 계산, 공유 상태 수정
  • 청크 크기 - 작은 작업은 큰 청크, 큰 작업은 작은 청크
  • 측정 필수 - 실제로 빨라지는지 항상 측정
SUMMARY

핵심 요약

핵심 포인트
  • ParallelFor - for 루프를 여러 스레드에 분배
  • 독립적 반복 필수 - 공유 상태 수정 금지
  • 인덱스별 결과 저장 - Results[Index] 패턴 사용
  • 청크 크기 조절 - 작은 작업은 큰 청크로 묶기
  • 최소 1000+ 반복 - 오버헤드 대비 이점 확보
  • 블로킹 - 모든 작업 완료까지 대기
PRACTICE

도전 과제

배운 내용을 직접 실습해보세요

실습 1: ParallelFor 기본 사용

ParallelFor(NumElements, Lambda)를 사용하여 다수의 NPC AI 업데이트를 병렬로 처리하세요. 각 NPC의 거리 계산, 시야 체크를 병렬화하고, 직렬 실행 대비 성능 향상을 측정하세요.

실습 2: ParallelFor with 최소 배치 크기

ParallelFor의 MinBatchSize 파라미터를 조정하여 오버헤드와 병렬화 이득의 균형을 찾으세요. 워크로드가 작을 때(10개 미만) 직렬 실행이 더 빠른 경우를 확인하세요.

심화 과제: 대규모 RPG 시뮬레이션 병렬화

수백 명의 NPC 행동, 수천 개의 환경 오브젝트 상태를 ParallelFor로 업데이트하세요. Game Thread에서의 결과 수집을 FCriticalSection 또는 Atomic 연산으로 안전하게 처리하세요.