
ECS(Entity-Component-System)란 무엇인가?
ECS는 Entity(엔티티), Component(컴포넌트), System(시스템)으로 구성된 아키텍처로, 게임 엔진 및 데이터 중심 애플리케이션 개발에서 널리 사용되는 설계 패턴입니다.
1. Entity (엔티티)
역할:
고유한 ID로 식별되는 객체.
엔티티 자체는 데이터를 포함하지 않으며, 단순히 컴포넌트를 조합하기 위한 컨테이너 역할을 합니다.
예시:
게임 캐릭터, 몬스터, 아이템과 같은 객체는 각각 엔티티로 표현됩니다.
2. Component (컴포넌트)
역할:
엔티티의 데이터를 저장하는 구조체 또는 클래스.
하나의 컴포넌트는 특정 데이터를 나타내며, 동작에 대한 코드는 포함하지 않습니다.
예시:
위치 데이터:
struct Position { float x, y; }체력 데이터:
struct Health { int current, max; }
3. System (시스템)
역할:
컴포넌트를 조작하거나 처리하는 로직.
특정 컴포넌트를 가진 엔티티를 반복적으로 처리하며, 데이터를 읽고 수정합니다.
예시:
물리 시스템:
Position컴포넌트를 업데이트.렌더링 시스템: 화면에
Position을 기준으로 엔티티를 그립니다.
ECS의 장점
고성능 데이터 접근:
ECS는 데이터 중심 설계(Data-Oriented Design)를 기반으로 하며, 데이터가 메모리에서 연속적으로 저장되므로 CPU 캐시 효율성이 높습니다.
예를 들어,
Position컴포넌트 배열은 연속적인 메모리 상에 저장되어 반복문 처리 시 매우 빠릅니다.
유연성:
엔티티에 필요한 컴포넌트만 추가하여, 객체 지향 설계(OOP)의 복잡한 상속 구조를 피할 수 있습니다.
새로운 기능을 추가할 때 기존 코드를 수정하지 않고 컴포넌트를 추가하면 됩니다.
유지보수성:
로직(
System)과 데이터(Component)가 분리되어, 코드가 읽기 쉽고 테스트하기 쉬워집니다.
확장성:
엔티티 수와 컴포넌트 종류가 증가해도 쉽게 확장 가능합니다.
ECS의 단점
학습 곡선:
ECS는 객체 지향 프로그래밍(OOP)과 다른 접근 방식이므로 초보자에게 다소 어려울 수 있습니다.
초기 설계 비용:
ECS는 데이터와 로직의 명확한 분리가 필요하므로, 초기 설계에 더 많은 시간이 걸릴 수 있습니다.
기존 방식의 컴포넌트 시스템과의 차이
1. 기존 방식 구조
유니티, 언리얼과 같은 OOP 기반 엔진에서:
각 게임 객체(GameObject 또는 Actor)는 고유한 상태와 메서드를 가진 클래스 객체로 표현됩니다.
컴포넌트는 게임 객체의 속성과 동작을 포함하는 서브클래스입니다.
객체는 상속과 다형성을 사용하여 설계됩니다.
예시: 유니티 방식
public class Player : MonoBehaviour {
public float health;
public float speed;
void Move() {
// 이동 로직
}
void TakeDamage(float damage) {
health -= damage;
}
}
성능상의 한계
1-1. 메모리 비효율성
객체 지향 설계는 객체의 메타데이터(가상 함수 테이블, 포인터 등)를 저장하기 위해 추가 메모리를 사용합니다.
게임 객체마다 고유한 메서드와 상태를 저장하기 때문에, 데이터가 메모리 상에서 연속적이지 않게 배치됩니다.
결과적으로, 캐시 미스(cache miss)가 발생하여 CPU 성능이 저하됩니다.
1-2. 캐시 비효율성
CPU는 데이터를 메모리에서 읽어올 때 연속적인 데이터(chunk)를 선호합니다.
OOP 방식에서는 객체들이 메모리 상에서 산발적으로 배치되므로, 동일한 유형의 데이터를 반복적으로 처리할 때 캐시 활용도가 낮습니다.
1-3. 다형성으로 인한 런타임 오버헤드
OOP 기반 엔진은 상속과 다형성을 통해 기능을 확장합니다.
다형성 사용 시, 런타임에 가상 함수 호출을 통해 메서드를 실행해야 하므로 성능 오버헤드가 발생합니다.
2. ECS의 성능 개선
성능상의 장점
2-1. 데이터 중심 설계로 인한 캐시 효율성
ECS는 동일한 유형의 데이터를 메모리 상에서 연속적으로 배치합니다.
이를 통해 CPU는 데이터를 캐시에 효과적으로 로드하여, 반복적인 작업에서 캐시 미스(cache miss)를 줄임.
예시:
기존 방식:

메모리 상에서 객체별로 데이터가 섞여 있음.
기존 방식에 대한 구체적인 접근
2-2. 로직과 데이터의 분리
객체 단위의 메모리 그룹화:
기존 OOP 방식에서는 보통 객체(플레이어, 몬스터, 아이템) 단위로 풀(pool)을 관리합니다.
이러한 풀은 동일한 유형의 객체를 묶어두는 구조이므로, 컴포넌트들이 서로 인접하게 배치될 가능성이 낮습니다.
각 객체의 컴포넌트는 동적 할당되기 때문에, 실제로는 객체 내부의 컴포넌트조차 메모리에서 분리될 수 있습니다.
따라서 이를 엔진 단위에서 메모리 할당 방식을 재설계 하지 않는다면 실제 데이터는 위 풀의 메모리 예시보다 더 파편적으로 존재할 수 있습니다.
ECS 방식:

컴포넌트별로 데이터를 정렬하여 한 번에 처리 가능.
ECS 방식의 구체적인 접근은 다른 글 참조
ECS는 로직(
System)과 데이터를 완전히 분리합니다.시스템은 데이터(컴포넌트)를 독립적으로 처리하며, 다형성을 제거하여 런타임 오버헤드를 줄입니다.
2-3. 병렬 처리
데이터가 연속적으로 정렬되어 있으므로, 멀티스레드 환경에서 병렬 처리 최적화를 쉽게 적용할 수 있습니다.
3. 성능 차이를 구체적으로 보여주는 예시
메모리 접근 패턴 차이
기존 방식:
객체 데이터가 비연속적으로 배치되어, 반복문에서 다수의 캐시 미스가 발생.
Player Pool : [Player1(Position), Player2(Position), ...]
Monster Pool : [Monster1(Position), Monster2(Position), ...]
Item Pool : [Item1(Position), Item2(Position), Item3(Position), ...]
각각의 엔티티 데이터를 읽기 위해 다른 메모리 주소에 접근해야 함.
ECS 방식:
동일한 컴포넌트를 가진 데이터가 연속적으로 배치되어 캐시 효율 극대화.
Position Component Pool : [Position1, Position2, Position3, ...]
CPU는 한 번의 메모리 접근으로 다수의 컴포넌트를 캐시에 로드 가능.
병렬 처리 효율성
기존 방식:
객체별로 상태와 메서드가 포함되어 있어, 병렬화에 적합하지 않음.
ECS 방식:
동일한 유형의 데이터를 처리하므로, 병렬 처리가 자연스럽게 이루어짐.
예시: 1000개의 포지션 데이터를 업데이트할 때, 스레드를 나누어 병렬적으로 처리 가능.
4. 결론
ECS가 기존 방식보다 성능이 뛰어난 이유는 다음과 같습니다:
데이터 중심 설계:
데이터가 연속적으로 배치되어, 캐시 효율성이 극대화됩니다.
로직과 데이터 분리:
다형성을 제거하여 런타임 오버헤드를 줄이고, 유지보수성과 확장성을 향상합니다.
병렬 처리와 최적화 용이성:
연속된 데이터 덕분에 병렬 처리가 용이해집니다.
결과적으로, ECS는 대규모 엔티티와 컴포넌트를 효율적으로 처리해야 하는 게임 엔진이나 데이터 중심 애플리케이션에서 기존 방식에 비해 훨씬 더 나은 성능을 제공합니다.