PART 8 - 강의 3/6
ParallelFor
대량 데이터의 병렬 처리로 CPU 코어 최대 활용
01
ParallelFor란?
for 루프의 병렬화
ParallelFor는 반복 작업을 여러 스레드에 자동으로 분배합니다. 각 반복이 독립적이라면 선형적 성능 향상을 기대할 수 있습니다.
일반 For Loop
0
1
2
3
4
5
6
7
단일 스레드에서 순차 실행
ParallelFor (4 스레드)
0
1
2
3
4
5
6
7
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 연산으로 안전하게 처리하세요.