엔터티 구성 요소 시스템 게임 엔진에서 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.