12. 데이터 지향 설계(DOD): AoS vs SoA와 캐시 친화적 레이아웃
난이도 중내 답안
모범답안
해설 — 데이터 지향 설계(DOD): AoS vs SoA와 캐시 친화적 레이아웃
난이도: 중
1. AoS vs SoA
AoS(Array of Structs) — 엔티티 단위로 필드가 묶여 연속:
[ {pos,vel,hp,name,...}_0 ][ {pos,vel,hp,name,...}_1 ]...
SoA(Struct of Arrays) — 같은 필드끼리 연속 배열:
pos[]: pos_0 pos_1 pos_2 ...
vel[]: vel_0 vel_1 vel_2 ...
hp[]: hp_0 hp_1 hp_2 ...
"매 틱 위치만 갱신"하는 루프에서는 SoA 가 유리하다. SoA 면 pos[] 만 연속으로 읽어 캐시라인이 100% 위치 데이터로 채워진다. AoS 면 한 엔티티의 pos 를 읽을 때 같은 캐시라인에 딸려 온 vel/hp/name 등 이번 틱에 안 쓰는 필드까지 적재되어, 캐시라인의 상당 부분이 낭비되고 그만큼 더 많은 라인을 끌어와야 한다.
2. 캐시라인·공간 지역성
CPU 는 메모리를 바이트가 아니라 캐시라인(보통 64B) 단위로 가져온다. 순차 접근을 감지하면 하드웨어 프리페처가 다음 라인들을 미리 끌어와 미스를 숨긴다 — SoA 의 연속 pos[] 순회가 프리페처에 이상적이다.
128B 엔티티에서 12B(예: float3 위치)만 매 틱 읽는 경우, AoS:
- 한 캐시라인(64B)에 엔티티가 0.5개 들어가고, 그 64B 중 유효한 위치는 12B 뿐 → 대역의 약 81%가 낭비(쓸데없는 필드 적재). 엔티티당 평균 2개 라인을 건드린다.
- SoA
pos[]면 64B 라인에 위치 5.3개가 들어가 라인의 거의 전부가 유효 데이터 → 메모리 대역·캐시 미스가 몇 배 절감.
3. 데이터 지향 설계(DOD)의 사고법
- 객체 중심(OOP): 엔티티마다 객체(힙에 흩어진), 가상함수로
Update()디스패치, 컴포넌트를 포인터로 추적. 직관적이지만 순회 시 포인터 추적(pointer chasing) 으로 캐시 미스가 잦고, 가상 호출마다 vtable 적재 + 간접 분기로 분기예측이 빗나간다. - DOD: "어떤 변환을 어떤 데이터 묶음에 적용하나"로 사고. 같은 종류 데이터를 연속 배열로 두고 타입별로 일괄(batch) 처리. 분기·가상호출을 줄이고(같은 코드 경로를 직선으로), 캐시·프리페처·SIMD 친화적.
- 비용: 포인터 추적은 매번 의존적 로드(직전 로드 결과로 다음 주소 계산)라 지연을 못 숨긴다. 가상 디스패치는 간접 분기 미스(수십 사이클)와 인라인 불가. 대량 순회에서 이 둘이 누적되면 치명적.
4. 트레이드오프 — SoA가 늘 답은 아니다
- 임의 접근(한 엔티티의 모든 필드): SoA 는 그 엔티티의 pos/vel/hp 가 서로 다른 배열의 같은 인덱스에 흩어져 있어, 한 엔티티를 통째로 다루면 여러 라인을 건드려 오히려 불리.
- 삽입/삭제: 엔티티 추가/제거 시 모든 배열을 동기화해 갱신해야(인덱스 정합성) 복잡. swap-and-pop, 세대(generation) 인덱스 등이 필요.
- 코드 복잡도/가독성: 필드별 배열 관리로 코드가 장황해지고 실수 여지 증가.
- 절충안: 핫-콜드 분리(hot-cold splitting) — 매 틱 쓰는 핫 필드(pos/vel)만 SoA(또는 별도 연속 배열)로, 드물게 쓰는 콜드 필드(name/설명)는 따로. 부분 SoA(AoSoA, 타일 단위로 SoA) 로 SIMD 폭에 맞춰 절충하기도. 즉 접근 패턴에 따라 레이아웃을 고른다.
5. 실무 적용 (MMORPG 서버)
- 대량 순회는 핫 필드 SoA 로: 이동 통합(pos += vel·dt), AoI 갱신, 광역 충돌/근접 질의는 위치 배열만 연속 순회 → 캐시·프리페치·SIMD 활용.
- 공간 분할 + 정렬: 그리드/셀 단위로 엔티티를 공간 지역성에 맞게 정렬·재배치(Z-order/모턴 코드)하면, 인접 엔티티가 메모리에서도 인접해 질의 시 미스 감소.
- 프리페치/배치: 순차 접근을 보장해 하드웨어 프리페처가 동작하게 하고, 필요시 소프트웨어 프리페치. 같은 처리(데미지 적용·버프 틱)는 타입별로 모아 일괄 실행해 분기·가상호출 제거.
- 컴포넌트 풀(ECS): 컴포넌트를 타입별 연속 풀에 두는 ECS 가 사실상 SoA·DOD 의 실천형. 시스템이 컴포넌트 배열을 직선으로 순회.
면접 포인트
- 매 틱 일부 필드만 대량 순회하면 SoA 가 캐시라인을 유효 데이터로 채워 AoS 대비 메모리 대역·미스를 크게 줄인다.
- CPU 는 캐시라인(64B) 단위 + 프리페처로 동작한다 — 순차·연속 접근이 핵심. AoS 는 안 쓰는 필드까지 적재해 대역을 낭비한다.
- SoA/DOD 가 만능은 아니다. 접근 패턴(대량 순회 vs 임의 접근) 에 맞춰 핫-콜드 분리·부분 SoA 로 절충하고, 포인터 추적·가상 디스패치를 줄이는 게 큰 그림이다.