개요
데이터 지역성은 캐쉬 효율을 극대화 하기위한 패턴이다. 메모리는 접근 속도가 느리기에 한 데이터에 접근시 그 데이터와 인접한 데이터 블럭을 캐쉬로 가져와 저장해둔다. 캐쉬에 저장된 데이터에 접근하면 속도가 훨씬 빠르다. 성능이 중요한 부분은 데이터 지역성을 신경쓰면 좋다.
사용 예시
연속 배열
만약 각 객체별로 여러 컴포넌트를 가리키는 포인터가 있고 그러한 객체들의 포인터를 가진 전역 객체 포인터 배열이 있다고하자. 그리고 각 데이터들은 힙에 랜덤하게 할당되어있다고 하자. 그리고 게임 루프는 각 객체의 AI 컴포넌트들을 순회하며 업데이트하고, 그 다음 물리 컴포넌트들을 순회하고...하는 식으로 컴포넌트들끼리 묶어 처리한다고 하자. 이렇게 되면 캐쉬 효율이 매우 낮다. ai 업데이트들을 처리할때 한 ai 업데이트마다 객체의 포인터를 따라 객체를 캐싱하고 또 그 객체에서 ai 컴포넌트를 캐싱할 것이다. 매번 두번의 캐싱이 발생하고 매우 비효율적이다. 이를 각 컴포넌트들의 데이터를 바로 저장하는 컴포넌트 배열로 저장하고 바로 접근하여 컴포넌트들을 처리하면 훨씬 효율적이다. 바로 컴포넌트 배열을 통해 해당 컴포넌트 데이터에 접근하고 인접한 컴포넌트들도 같이 가져와 캐쉬 히트가 많이 날 것이다.
이때 여러 컴포넌트들을 가진 개체를 어떻게 정의할지도 고민해봐야한다. 각 컴포넌트의 포인터를 가지는 클래스로 관리할 수도 있지만 접근이 빠른대신 해당 컴포넌트들의 배열위에서의 위치를 수정하기 어렵다. 포인터 대신 컴포넌트의 ID를 가르키게 할 수도 있다. 아니면 완전히 개체 클래스를 사용하지 않고 개체를 ID로만 정의한 뒤 각 컴포넌트가 자신을 소지한 개체 ID를 가지도록 할 수도 있다.
비활성화 처리
위 방식에서 우선 최대 개수로 컴포넌트 배열을 만들고 실제 사용되는 컴포넌트들을 활성화 시키는 식으로 구현할 것이다. 이때 컴포넌트에 활성화 플래그를 달고 이를 통해 활성화된 컴포넌트만 업데이트하는식으로 구현할수도 있지만 이는 캐쉬 효율적이지 못하다. 활성화 플래그를 참조하는 시점에 해당 컴포넌트는 캐싱될 것이고 무의미한 캐쉬가 매번 발생해 캐쉬가 비효율적으로 처리된다. 따라서 활성화된 컴포넌트를 배열 앞쪽에 모으고 그 구간만 처리하는 식으로 하면 훨씬 효율적이다.
빈번한 데이터와 한산한 데이터 나누기
만약 AI 컴포넌트에 target 벡터와 dropItem과 관련된 여러 데이터가 있다고 하자. target 벡터는 업데이트에서 매 프레임 참조하지만 dropItem 관련 데이터는 죽을때 딱 한번 참조된다고 하자. 이때 dropItem데이터를 AI 컴포넌트에 그대로 포함시키면 매번 업데이트에서 AI컴포넌트 전체 데이터를 캐쉬에 올려 비효율적일 것이다. 이를 개선하기 위해 dropItem관련 데이터를 다른 구조체또는 클래스에 모으고 이에 대한 포인터만 AI 컴포넌트가 가지도록 하면 된다. 그러면 업데이트시에는 포인터하나만 캐쉬하여 훨씬 효율적이다. dropItem관련 데이터에 접근할때는 포인터를 한번 거쳐야하긴 하지만 접근이 매우 적기때문에 득이 실보다 훨씬 크다.
참고서적
게임 프로그래밍 패턴
'디자인 패턴 > 게임 프로그래밍 패턴' 카테고리의 다른 글
[게임 프로그래밍 패턴][19]객체 풀 (0) | 2024.05.11 |
---|---|
[게임 프로그래밍 패턴][18]더티 플래그 (0) | 2024.05.10 |
[게임 프로그래밍 패턴][16]서비스 중개자 (0) | 2024.05.05 |
[게임 프로그래밍 패턴][15]이벤트 큐 (0) | 2024.05.05 |
[게임 프로그래밍 패턴][14]컴포넌트 패턴 (0) | 2024.05.01 |