엔터티 구성 요소 시스템 게임 엔진에서 CPU 캐시를 활용하는 방법은 무엇입니까?


15

CPU 캐시를 현명하게 사용하기에 적합한 아키텍처 인 ECS 게임 엔진 설명서를 자주 읽습니다.

그러나 CPU 캐시의 이점을 어떻게 얻을 수 있는지 알 수 없습니다.

구성 요소가 연속 메모리에 배열 (또는 풀)로 저장된 경우 구성 요소를 순차적으로 읽는 경우에만 CPU 캐시 BUT을 사용하는 것이 좋습니다.

시스템을 사용할 때 특정 유형의 구성 요소가있는 엔티티 목록 인 엔티티 목록이 필요합니다.

그러나 이러한 목록은 구성 요소를 순차적이 아닌 임의의 방식으로 제공합니다.

그렇다면 캐시 적중을 극대화하기 위해 ECS를 설계하는 방법은 무엇입니까?

편집하다 :

예를 들어, Physic 시스템에는 RigidBody 및 Transform 구성 요소가있는 엔터티에 대한 엔터티 목록이 필요합니다 (RigidBody 용 풀 및 Transform 구성 요소 풀이 있음).

따라서 엔터티를 업데이트하는 루프는 다음과 같습니다.

for (Entity eid in entitiesList) {
    // Get rigid body component
    RigidBody *rigidBody = entityManager.getComponentFromEntity<RigidBody>(eid);

    // Get transform component
    Transform *transform = entityManager.getComponentFromEntity<Transform>(eid);

    // Do something with rigid body and transform component
}

문제는 entity1의 RigidBody 구성 요소가 풀의 인덱스 2에 있고 pool의 인덱스 0에 entity1의 Tranform 구성 요소가 될 수 있다는 것입니다. / 구성 요소를 무작위로).

따라서 구성 요소가 메모리에서 연속적 일지라도 무작위로 읽히므로 더 많은 캐시 미스가 발생합니다.

루프에서 다음 컴포넌트를 프리 페치하는 방법이 없다면?


각 구성 요소를 어떻게 할당하고 있는지 보여줄 수 있습니까?
concept3d

간단한 풀 할당 자 및 풀에서 컴포넌트의 재배치를 관리하기위한 컴포넌트 참조를위한 핸들 관리자 (컴포넌트를 메모리에 인접하게 유지).
Johnmph

루프 예에서는 구성 요소 업데이트가 엔티티마다 인터리브된다고 가정합니다. 대부분의 경우 컴포넌트 유형별로 컴포넌트를 대량으로 업데이트 할 수 있습니다 (예 : 모든 rigidbody 컴포넌트를 먼저 업데이트 한 다음 완성 된 rigidbody 데이터로 모든 변환을 업데이트 한 다음 새 변환으로 모든 렌더링 데이터를 업데이트하십시오 ...)-캐시를 개선 할 수 있습니다 각 구성 요소 업데이트에 사용합니다. 이 유형의 구조는 Nick Wiggill이 제안하는 것입니다.
DMGregory

사실 나쁜 예입니다. 실제로 물리 시스템보다 "완성 된 강체 데이터로 모든 변환 업데이트"시스템에 가깝습니다. 그러나 이러한 시스템 (강체로 업데이트 변환, 변환으로 렌더링 렌더링 등)에서 문제는 동일하게 유지됩니다. 우리는 동시에 여러 유형의 구성 요소를 가져야합니다.
Johnmph

이것도 관련이 있는지 확실하지 않습니까? gamasutra.com/view/feature/6345/…
DMGregory

답변:


13

Mick West의 기사 는 엔터티 구성 요소 데이터를 선형화하는 프로세스를 전체적으로 설명합니다. 그것은 성능을 크게 향상시키기 위해 오늘날보다 훨씬 덜 인상적인 하드웨어에서 수년 전 Tony Hawk 시리즈에서 작동했습니다. 그는 기본적으로 각기 다른 유형의 엔터티 데이터 (위치, 점수 및 기타)에 대해 사전 할당 된 전역 배열을 사용했으며 시스템 전체 update()기능 의 뚜렷한 단계에서 각 배열을 참조 합니다. 각 엔터티에 대한 데이터가 이러한 각 전역 배열에서 동일한 배열 인덱스에 있다고 가정 할 수 있습니다. 예를 들어 플레이어가 먼저 생성되면 [0]각 배열에 데이터가있을 수 있습니다 .

캐시 최적화에 더욱 특화된 Christer Ericsson의 C 및 C ++ 슬라이드 .

좀 더 자세하게 설명하려면 각 데이터 유형 (예 : 위치, xy 및 z)마다 연속 메모리 블록 (배열로 가장 쉽게 할당 됨)을 사용하여 각 데이터 블록을 개별적으로 활용하여 참조의 좋은 지역성을 보장해야합니다. update()즉, 특정 update()호출 내에서 재사용하려는 데이터를 재사용하기 전에 하드웨어의 LRU 알고리즘을 통해 캐시가 플러시되지 않도록하는 시간적 지역성을위한 단계입니다 . 암시 한 것처럼 new각 엔티티 인스턴스에 서로 다른 유형의 데이터가 인터리브되어 참조의 지역성이 줄어들 기 때문에 엔티티와 컴포넌트를 개별 객체로 할당하는 것은 원하지 않습니다 .

구성 요소 (데이터)간에 상호 의존성이있어 관련 데이터 (예 : Transform + Physics, Transform + Renderer)와 일부 데이터를 분리 할 수없는 경우 물리 및 렌더러 배열 모두에서 Transform 데이터를 복제하도록 선택할 수 있습니다. 모든 관련 데이터가 각 성능에 중요한 작업에 대한 캐시 라인 너비에 맞는지 확인합니다.

또한 L2 및 L3 캐시 (대상 플랫폼에서이를 가정 할 수있는 경우)는 제한적인 라인 너비와 같이 L1 캐시가 겪을 수있는 문제를 완화하기 위해 많은 작업을 수행합니다. 따라서 L1 미스에서도 주요 메모리에 대한 콜 아웃을 가장 자주 방지하는 안전망이며, 이는 모든 캐시 레벨에 대한 콜 아웃보다 훨씬 느린 속도입니다.

데이터 쓰기 에 대한 참고 사항 쓰기는 기본 메모리를 불러 오지 않습니다. 기본적으로 오늘날의 시스템에는 후기 입 캐싱이 활성화되어 있습니다. 값을 쓰면 기본 메모리가 아닌 캐시에 (처음에는) 쓰기 만하므로 병목 현상이 발생하지 않습니다. 주 메모리에서 데이터가 요청되고 (캐시에있는 동안에는 발생하지 않음) 오래된 메모리 인 경우에만 주 메모리가 캐시에서 업데이트됩니다.


1
C ++을 처음 접하는 사람을위한 참고 사항 : std::vector기본적으로 동적으로 크기를 조정할 수있는 배열이므로 연속적입니다 (실제로 이전 C ++ 버전에서는 새로운 C ++ 버전에서는 jure입니다). 일부 구현은 std::deque"충분히 연속적"입니다 (Microsoft는 아니지만).
Sean Middleditch 2014

2
@Johnmph 아주 간단하게 : 당신이 참조 할 수있는 지역이 없다면 아무 것도 없습니다. 공간 및 물리 정보와 같이 두 개의 데이터가 밀접하게 관련되어있는 경우 즉, 함께 처리되는 경우 인터리빙 된 단일 구성 요소로 압축해야 할 수도 있습니다. 그러나 공간 데이터를 활용하는 다른 논리 (예 : AI)는 공간 데이터와 함께 포함되지 않은 결과로 어려움을 겪을 수 있습니다 . 따라서 가장 성능이 필요한 항목에 따라 다릅니다 (물론 물리학). 말이 돼?
엔지니어

1
@ Johnmph 네, Nick과 완전히 동의합니다. Nick은 메모리에 저장되는 방식에 관한 것입니다. 메모리에 멀리 떨어져있는 두 개의 구성 요소에 대한 포인터가있는 엔티티가 있으면 로컬이 없으므로 캐시 라인에 맞아야합니다.
concept3d

2
@ Johnmph : 실제로 Mick West의 기사는 최소한의 상호 의존성을 가정합니다. 따라서 : 종속성을 최소화하십시오. 예를 들어, 당신이 그 종속성을 최소화 할 수 캐시 라인을 따라 복제 데이터는 ... 함께 변환 등이 모두 강체 렌더링; 캐시 라인에 맞추려면 가능한 한 데이터 원자를 줄여야 할 수도 있습니다. 소수점 이하의 값당 부동 소수점에서 고정 소수점 (4 바이트 대 2 바이트)으로 부분적으로 이동할 수 있습니다. 그러나 어떤 방식 으로든 데이터를 최대한 활용하려면 concept3d에 명시된대로 캐시 라인 너비에 맞아야합니다.
엔지니어

2
@ 존프. 아니요. Transform 데이터를 쓸 때마다 단순히 두 배열 모두에 씁니다. 걱정해야 할 글이 아닙니다. 당신이 쓰기를 보내면, 그것은 마치 좋은 것입니다. Physics and Renderer를 실행할 때 나중에 업데이트에서 읽은 것은 읽기 가 바로 CPU에 가까운 단일 캐시 라인에서 모든 관련 데이터에 즉시 액세스 해야합니다 . 또한, 실제로 모두 함께 필요한 경우 추가 복제를 수행하거나 물리를 확인하고 단일 캐시 라인에 맞게 변환 및 렌더링합니다. 64 바이트가 일반적이며 실제로는 많은 데이터입니다! ...
엔지니어
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.