엔터티 시스템은 어떻게 캐시 효율적입니까?


32

최근엔 엔터티 시스템에서 C ++ / OpenGL 게임 엔진에 구현하기 위해 많은 독서를 해왔습니다. 엔터티 시스템에 대해 끊임없이 찬사를받는 두 가지 주요 이점은 다음과 같습니다.

  1. 복잡한 상속 계층 구조에 얽매이지 않아도되므로 새로운 유형의 개체를 쉽게 구성 할 수 있습니다.
  2. 캐시 효율성으로 인해 이해가 어렵습니다.

물론 이론은 간단하다. 각 구성 요소는 메모리 블록에 연속적으로 저장되므로 해당 구성 요소를 관리하는 시스템은 메모리를 뛰어 넘어 캐시를 죽이지 않고도 전체 목록을 반복 할 수 있습니다. 문제는 이것이 실제로 실용적인 상황을 실제로 생각할 수 없다는 것입니다.


먼저 구성 요소가 저장되는 방법과 서로 참조하는 방법을 살펴 보겠습니다. 시스템은 둘 이상의 구성 요소를 사용할 수 있어야합니다. 즉 렌더링 및 물리 시스템 모두 변환 구성 요소에 액세스해야합니다. 나는 이것을 해결하는 여러 가지 가능한 구현을 보았지만 그중 어느 것도 잘하지 못한다.

컴포넌트가 다른 컴포넌트에 대한 포인터를 저장하거나 컴포넌트에 대한 포인터를 저장하는 엔티티에 대한 포인터를 저장할 수 있습니다. 그러나 포인터를 믹스에 넣 자마자 이미 캐시 효율성을 저하시키고 있습니다. 모든 구성 요소 배열이 'n'이 큰지 확인할 수 있습니다. 여기서 'n'은 시스템에 존재하는 엔티티 수입니다. 그러나이 방법은 메모리를 많이 낭비합니다. 이렇게하면 엔진에 새 구성 요소 유형을 추가하기가 매우 어려워 지지만 한 어레이에서 다른 어레이로 점프하기 때문에 여전히 캐시 효율성을 떨어 뜨립니다. 별도의 배열을 유지하는 대신 엔티티 배열을 인터리브 할 수 있지만 여전히 메모리를 낭비하고 있습니다. 새로운 구성 요소 나 시스템을 추가하는 것은 엄청나게 비싸지 만, 이제는 모든 이전 레벨을 무효화하고 파일을 저장한다는 추가 이점이 있습니다.

이것은 엔터티가 목록, 모든 프레임 또는 틱에서 선형으로 처리된다고 가정합니다. 실제로는 종종 그렇지 않습니다. 오 클루 전 컬링을 수행하기 위해 섹터 / 포털 렌더러 또는 옥트리를 사용한다고 가정합니다. 섹터 / 노드 내에 엔터티를 연속적으로 저장할 수는 있지만 원하는지 여부에 관계없이 뛰어 넘을 것입니다. 그런 다음 다른 순서로 저장된 엔티티를 선호하는 다른 시스템이 있습니다. AI LOD 작업을 시작할 때까지 엔터티를 큰 목록에 저장하면 AI가 정상일 수 있습니다. 그런 다음 플레이어까지의 거리 또는 다른 LOD 지표에 따라 해당 목록을 분할해야합니다. 물리학은 그 옥트리를 사용하려고합니다. 스크립트는 상관없이 실행해야합니다.

"logic"(예 : 인공 지능, 스크립트 등)과 "world"(예 : 렌더링, 물리, 오디오 등)간에 구성 요소를 분리하고 각 목록을 개별적으로 관리하는 것을 볼 수 있지만 이러한 목록은 여전히 ​​서로 상호 작용해야합니다. AI가 엔티티 렌더링에 사용 된 변환 또는 애니메이션 상태에 영향을 줄 수없는 경우 무의미합니다.


실제 게임 엔진에서 엔티티 시스템은 어떻게 "캐시 효율성"입니까? 어쩌면 모두 배열에 엔터티를 저장하고 octree 내에서 참조하는 것과 같이 모두가 사용하고 있지만 이야기하지 않는 하이브리드 접근법이 있습니까?


요즘에는 멀티 코어 CPU가 있으며 한 행보다 큰 캐시를 사용하십시오. 두 시스템의 정보에 액세스해야하더라도 두 시스템 모두에 적합 할 수 있습니다. 또한 그래픽 렌더링은 종종 당신이 언급 한 것 (나무, 장면, ..)에 따라 분리됩니다.
wondra

2
엔터티 시스템은 항상 캐시 효율적인 것은 아니지만 일부 구현의 경우 (다른 방법으로 유사한 작업을 수행 할 수 있음) 이점이 될 수 있습니다.
Josh

답변:


43

엔터티 시스템에 대해 끊임없이 칭찬받는 두 가지 주요 이점은 1) 복잡한 상속 계층 구조와 얽 히지 않아도되므로 새로운 종류의 엔터티를 쉽게 구성 할 수 있으며 2) 캐시 효율성입니다.

(1)은 ES / ECS뿐만 아니라 구성 요소 기반 설계 의 이점입니다 . "시스템"부분이없는 여러 가지 방법으로 구성 요소를 사용할 수 있으며 제대로 작동합니다 (인디 및 AAA 게임 모두 그러한 아키텍처를 사용합니다).

표준 Unity 오브젝트 모델 (사용 GameObjectMonoBehaviour오브젝트)은 ECS가 아니라 컴포넌트 기반 설계입니다. 최신 Unity ECS 기능은 물론 실제 ECS입니다.

시스템은 둘 이상의 구성 요소를 사용할 수 있어야합니다. 즉 렌더링 및 물리 시스템 모두 변환 구성 요소에 액세스해야합니다.

일부 ECS는 구성 요소 컨테이너를 엔티티 ID별로 정렬합니다. 즉, 각 그룹의 해당 구성 요소는 동일한 순서를 갖습니다.

당신이 선형 적 요소 그래픽을 통해 반복하는 경우 당신이 있다는 것을이 수단 또한 선형 적으로 대응하는 변환 구성 요소를 통해 반복. 렌더링하지 않는 물리 트리거 볼륨을 가질 수 있기 때문에 일부 변환을 건너 뛸 수 있지만 항상 메모리에서 건너 뛰기 때문에 (특히 일반적으로 큰 거리는 아니지만) 여전히 진행 중입니다. 효율성 향상.

이는 HPC에 권장되는 접근 방식 (SOA)을 사용하는 방법과 유사합니다. CPU와 캐시는 단일 선형 배열을 처리 할 수있을뿐만 아니라 여러 선형 배열을 처리 할 수 ​​있으며 임의 메모리 액세스를 처리 할 수있는 것보다 훨씬 우수합니다.

Unity ECS를 포함하여 일부 ECS 구현에 사용되는 또 다른 전략은 해당 엔티티의 아키타 입을 기반으로 컴포넌트를 할당하는 것입니다. 즉, 구성 요소를 정확하게 설정 모든 엔티티 ( PhysicsBody, Transform) 다른 구성 요소와 엔티티 별도로 할당됩니다 (예를 들어 PhysicsBody, Transform, Renderable ).

이러한 설계의 시스템은 먼저 요구 사항과 일치하는 모든 아키 타입 (필요한 컴포넌트 세트가 있음)을 찾고 해당 아키 타입 목록을 반복하며 각 일치하는 아키 타입에 저장된 컴포넌트를 반복하여 작동합니다. 이를 통해 아키 타입 내에서 완전히 선형적이고 진정한 O (1) 컴포넌트 액세스가 가능하며 시스템은 잠재적으로 수십만 엔터티를 검색하는 대신 작은 아키 타입 목록을 검색하여 매우 낮은 오버 헤드로 호환 가능한 엔터티를 찾을 수 있습니다.

컴포넌트가 다른 컴포넌트에 대한 포인터를 저장하거나 컴포넌트에 대한 포인터를 저장하는 엔티티에 대한 포인터를 저장할 수 있습니다.

동일한 엔터티에서 다른 구성 요소를 참조하는 구성 요소는 아무것도 저장할 필요가 없습니다. 다른 엔티티에서 컴포넌트를 참조하려면 엔티티 ID를 저장하십시오.

단일 엔터티에 대해 구성 요소가 두 번 이상 존재할 수 있고 특정 인스턴스를 참조해야하는 경우 다른 엔터티의 ID와 해당 엔터티의 구성 요소 인덱스를 저장하십시오. 그러나 많은 ECS 구현에서는 이러한 경우를 허용하지 않습니다. 특히 이러한 작업의 효율성이 떨어지기 때문입니다.

모든 구성 요소 배열이 'n'이 큰지 확인할 수 있습니다. 여기서 'n'은 시스템에 존재하는 엔티티 수입니다.

포인터가 아닌 핸들 (예 : 인덱스 + 생성 마커)을 사용하면 객체 참조가 손상 될 염려없이 배열의 크기를 조정할 수 있습니다.

std::deque어떤 이유로 포인터를 허용하거나 문제를 측정 한 경우 많은 일반적인 구현 과 비슷한 "청크 배열"접근법 (배열 배열) 을 사용할 수 있습니다 어레이 크기 조정 성능.

둘째, 이것은 모든 엔티티가 프레임 / 틱마다 목록에서 선형으로 처리된다고 가정하지만 실제로는 그렇지 않습니다.

엔티티에 따라 다릅니다. 예, 많은 유스 케이스의 경우 사실이 아닙니다. 실제로 이것이 컴포넌트 기반 설계 (양호)와 엔티티 시스템 (특정 형태의 CBD) 의 차이를 강조하는 이유 입니다.

일부 구성 요소는 확실히 선형으로 처리하기 쉽습니다. 일반적으로 "트리 헤비 (tree heavy)"사용 사례에서도 엄격하게 패킹 된 어레이 (일반적으로 게임의 AI 에이전트와 같이 N이 최대 수백 개의 N을 포함하는 경우)를 사용하여 성능이 확실히 향상되었습니다.

일부 개발자는 또한 데이터 지향 선형 할당 데이터 구조를 사용하는 경우의 성능 이점이 "스마트 한"트리 기반 구조를 사용하는 경우의 성능 이점보다 높다는 것을 알게되었습니다. 물론 게임과 특정 사용 사례에 따라 다릅니다.

섹터 / 포털 렌더러 또는 octree를 사용하여 오 클루 전 컬링을 수행한다고 가정합니다. 섹터 / 노드 내에 엔터티를 연속적으로 저장할 수는 있지만 원하는지 여부에 관계없이 점프 할 것입니다.

배열이 여전히 얼마나 도움이되는지 놀랄 것입니다. "어디서나"보다 훨씬 작은 메모리 영역에서 뛰어 다니고 있으며 모든 점프에서도 캐시에 무언가가 생길 가능성이 훨씬 큽니다. 특정 크기 이하의 트리를 사용하면 전체 항목을 캐시로 미리 가져올 수 있으며 해당 트리에서 캐시 누락이 발생하지 않을 수도 있습니다.

단단히 포장 된 배열에 살도록 구축 된 트리 구조도 있습니다. 예를 들어, octree를 사용하면 힙 같은 구조 (자녀 앞에서 부모, 서로 형제)를 사용하여 나무를 "드릴 다운"할 때도 항상 배열에서 반복되는 것을 보장 할 수 있습니다. CPU는 메모리 액세스 / 캐시 조회를 최적화합니다.

중요한 점입니다. x86 CPU는 복잡한 짐승입니다. CPU는 머신 코드에서 마이크로 코드 옵티 마이저를 효과적으로 실행하여 작은 마이크로 코드 및 명령 순서 변경, 메모리 액세스 패턴 예측 등을 수행합니다. CPU 또는 캐시 작동 방식

그런 다음 다른 순서로 저장된 엔티티를 선호하는 다른 시스템이 있습니다.

여러 번 저장할 수 있습니다. 배열을 최소한의 세부 사항까지 제거하면이 방법으로 실제로 메모리를 절약 할 수 있습니다 (64 비트 포인터를 제거하고 더 작은 인덱스를 사용할 수 있기 때문에).

별도의 배열을 유지하는 대신 엔티티 배열을 인터리브 할 수는 있지만 여전히 메모리를 낭비하고 있습니다.

이것은 좋은 캐시 사용과는 반대입니다. 변환 및 그래픽 데이터 만 신경 쓰는 경우 물리 및 AI 및 입력 및 디버그 등에 대한 다른 모든 데이터를 머신이 가져 오는 데 시간을 허비하는 이유는 무엇입니까?

그것은 일반적으로 ECS vs 모 놀리 식 게임 객체를 선호하는 요점입니다 (다른 구성 요소 기반 아키텍처와 비교할 때 실제로 적용 할 수는 없지만).

가치가있는 것은 인터리브 스토리지를 사용하는 것으로 알고있는 대부분의 "프로덕션 급"ECS 구현입니다. 앞에서 언급 한 (예 : Unity ECS에서 사용 된) 널리 사용되는 아키 타입 접근 방식은 아키 타입과 관련된 컴포넌트에 인터리브 된 스토리지를 사용하도록 매우 명시 적으로 구축되었습니다.

AI가 엔티티 렌더링에 사용 된 변환 또는 애니메이션 상태에 영향을 줄 수 없다면 AI는 의미가 없습니다.

AI가 변환 데이터에 선형으로 효율적으로 액세스 할 수 없다고해서 다른 시스템에서 해당 데이터 레이아웃 최적화를 효과적으로 사용할 수있는 것은 아닙니다. 게임 로직 시스템이 일반적으로 수행하는 임시 방식의 게임 로직 시스템을 중단하지 않고도 데이터 변환에 팩형 배열을 사용할 수 있습니다.

또한 코드 캐시를 잊어 버렸습니다 . 순진한 구성 요소 아키텍처와 달리 ECS의 시스템 접근 방식을 사용하면 동일한 작은 코드 루프를 실행하고 가상 함수 테이블을 통해 무작위로 Update분산 된 임의의 함수로 앞뒤로 건너 뛰지 않습니다. 이진. 따라서 AI의 경우, 모든 다른 AI 구성 요소를 (물론 동작을 구성 할 수 있도록 둘 이상의 구성 요소가 있기 때문에) 실제로 별도의 버킷에 유지하고 최상의 코드 캐시 사용을 얻기 위해 각 목록을 개별적으로 처리하려고합니다.

지연된 이벤트 큐 (시스템이 이벤트 목록을 생성하지만 시스템이 모든 엔티티 처리를 완료 할 때까지 전달하지 않는 경우)를 사용하면 이벤트를 유지하면서 코드 캐시가 잘 사용되도록 할 수 있습니다.

각 시스템이 프레임에 대해 읽을 이벤트 큐를 알고있는 접근 방식을 사용하면 이벤트를 빠르게 읽을 수도 있습니다. 또는 적어도없는 것보다 빠릅니다.

성능은 절대가 아닙니다. 우수한 데이터 지향 설계의 성능 이점을보기 위해 마지막 단일 캐시 누락을 모두 제거 할 필요는 없습니다.

ECS 아키텍처 및 데이터 지향 디자인 패턴으로 많은 게임 시스템이 더 잘 작동하도록하는 연구가 여전히 진행 중입니다. 최근 몇 년 동안 SIMD로 수행 한 놀라운 일들 (예 : JSON 파서)과 마찬가지로, ECS 아키텍처를 사용하여 고전적인 게임 아키텍처에는 직관적이지 않지만 여러 가지를 제공하는 점점 더 많은 일들을보고 있습니다 이점 (속도, 멀티 스레딩, 테스트 가능성 등).

또는 모두가 사용하고 있지만 아무도 이야기하지 않는 하이브리드 접근법이있을 수 있습니다.

이것은 특히 ECS 아키텍처에 회의적 인 사람들을 위해 과거에 옹호 한 것입니다. 성능이 중요한 구성 요소에 대한 우수한 데이터 지향 접근 방식을 사용하십시오. 단순성이 개발 시간을 향상시키는 간단한 아키텍처를 사용하십시오. ECS가 제안한 것과 같이 모든 단일 구성 요소를 엄격한 구성 요소 정의로 엄격히 분류하지 마십시오. 이해하기 쉬운 ECS 유사 접근 방식을 쉽게 사용할 수 있고 ECS 유사 접근 방식이 이해가 안되는 단순한 구성 요소 구조 (또는 트리 구조보다 덜 이해되는 방식)를 사용하는 방식으로 컴포넌트 아키텍처를 개발하십시오. .

저는 개인적으로 ECS의 진정한 힘으로 비교적 최근에 개종했습니다. 저에게있어 결정 요소는 ECS에 대해 거의 언급되지 않았습니다. 과거에 작업했던 밀접하게 결합 된 로직 탑재 컴포넌트 기반 디자인에 비해 게임 시스템 및 로직에 대한 필기 테스트를 거의 사소하게 만듭니다. ECS 아키텍처는 구성 요소 만 소비하고 구성 요소 업데이트를 생성하는 모든 논리를 시스템에 배치하므로 시스템 동작을 테스트하기위한 "모의"구성 요소 집합을 작성하는 것은 매우 쉽습니다. 대부분의 게임 로직은 시스템 내부에만 존재하기 때문에 모든 시스템을 테스트하면 게임 로직의 코드 범위가 상당히 높아집니다. 시스템은 복잡성이나 성능에 미치는 영향이 훨씬 적은 테스트에 모의 종속성 (예 : GPU 인터페이스)을 사용할 수 있습니다.

옆으로, 많은 사람들이 ECS가 무엇인지 실제로 이해하지 못하고 ECS에 대해 이야기한다는 것을 알 수 있습니다. 나는 고전 Unity가 주파수를 낮추면서 ECS라고 불렀는데, 너무 많은 게임 개발자들이 "ECS"를 "Components"와 동일시하고 "Entity System"부분을 완전히 무시한다는 것을 보여줍니다. 많은 사람들이 실제로 실제 ECS가 아닌 구성 요소 기반 설계를 옹호 할 때 인터넷의 ECS에 많은 사랑이 쌓이는 것을 볼 수 있습니다. 이 시점에서 논쟁하는 것은 거의 의미가 없습니다. ECS는 원래의 의미에서 일반적인 용어로 손상되었으며 "ECS"가 "데이터 지향 ECS"와 같은 의미가 아님을 인정할 수도 있습니다. : /


1
일반적인 구성 요소 기반 설계와 비교 / 대비하려는 경우 ECS가 의미하는 바를 정의 (또는 연결)하는 것이 유용합니다. 나는 구별이 무엇인지 명확하지 않습니다. :)
Nathan Reed

답을 주셔서 감사합니다.이 주제에 대해 여전히 많은 연구가있는 것처럼 보입니다. 내가 지적 할 수있는 책이 있습니까?
하이든 V.하라 흐

3
@NathanReed : ECSentity-systems.wikidot.com/es-terminology 와 같은 곳에 문서화되어 있습니다 . 구성 요소 기반 디자인 은 상속에 대한 규칙적인 집계이지만 게임 디자인에 유용한 동적 구성에 중점을 둡니다. ECS 용어의 의미로 시스템 또는 엔터티를 사용하지 않는 구성 요소 기반 엔진을 작성할 수 있으며 게임 엔진에서 게임 객체 / 엔티티보다 훨씬 더 많은 구성 요소를 사용할 수 있으므로 차이점을 강조합니다.
Sean Middleditch '

2
이것은 웹상의 모든 문헌에도 불구하고 내가 읽은 ECS에 대한 최고의 게시물 중 하나입니다. 메가 엄지 손가락. 그렇다면 Sean은 궁극적으로 게임을 개발하기위한 일반적인 방법은 무엇입니까 (복잡한 것이 아니라)? 순수한 ECS? 컴포넌트 기반과 ECS의 혼합 된 접근? 나는 당신의 디자인에 대해 더 알고 싶습니다! Skype 또는 다른 것에 대해 논의하기 위해 당신을 붙잡는 것이 너무 많은가?
그림 쇼

2
@ 그림 쇼 : gamedev.net은 내가 생각하는 reddit.com/r/gamedev와 같이 더 개방적인 토론을하기에 알맞은 곳입니다 (나는 내가 redditer는 아니지만). 나는 다른 많은 밝은 사람들과 마찬가지로 gamedev.net에 자주 있습니다. 나는 일반적으로 일대일 대화를하지 않습니다. 나는 꽤 바빠서 다운 타임 (즉, 컴파일 링)을 선호하여 소수보다는 많은 사람들을 돕습니다. :)
Sean Middleditch
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.