
최대 54000개의 entity를 생성하고 프로파일링 중인 모습 unity editor loop를 제외하면 약 45 프레임은 안정적으로 방어할 수 있다
안영훈 포트폴리오 (상위 링크로 되돌아가기)
| 제목 | Unity ECS mini project |
|---|---|
| 개발 기간 | 2026.03.30 ~ 2026.04.08 (9일) |
| 인원 | 1인 |
| 깃허브 | ‣ |
| 개발 환경 | Unity ECS, 3D, C#, Github (Source Tree) |
| 개발 일지 (블로그) | https://blog.naver.com/dksdudgns666/224241944442 |
| 시뮬레이션 엔진 | Unity 3D Mono / ECS |
|---|---|
| 노트북 기종 | Msi GP75 Leopard 9SD |
| 프로세서 | Intel(R) i7 - 9750H CPU 2.60GHZ |
| OS | Windows 11 x64 |
| RAM | 16.0 GB (8GB x 2) |
| 그래픽 카드 0 | Intel(R) UHD Graphics 630 |
| 그래픽 카드 1 | NVIDIA GeForce GTX 1660 Ti전용 GPU |
| GPU 메모리 정보 | 메모리 6.0GB / 공유 GPU 메모리 7.9GB |
| 총 GPU 메모리 13.9GB |
본 프로젝트를 진행하며 코드 리뷰/리팩터링 툴 개발을 위해 CLI agent가 적극적으로 사용되었습니다. SweeperSystem_backup.cs은 제가 작성한 코드이고, SweeperSystem.cs은 해당 툴로 리팩터링한 코드입니다. 코드 리뷰/리팩터링 툴의 품질을 확인하기 위한 용도였음을 밝힙니다.
| • 핵심 로직 | |
|---|---|
| Entity Authoring 정의 및 Baking | • 데이터 정의 |
| IComponentData 인터페이스를 이용해 MonoBehaviour (유니티 엔진 컴포넌트) 상의 데이터를 정의하고 Baker 함수를 통해 ECS 데이터로 변환합니다. | |
| Entity System 정의 | • [BurstCompile] 옵션을 통한 최적화 |
| 해당 옵션은 CPU 연산 능력을 크게 끌어 올려줍니다. | |
| • Entity의 이동 및 파괴 구현 : IJob - Execute() 함수 | |
| IJob 인터페이스를 이용해 스레드 단위에서 처리할 객체의 실제 동작을 정의합니다. | |
| ECB(Entity Command Buffer)를 통해 실행 이전에 해당 Entity가 언제 파괴될지 예약합니다. (멀티 스레드가 동작하는 Job 내부에서 Entity를 생성/파괴하는 것은 금지되어 있습니다. ECB를 통해 안전하게 동작하도록 처리합니다.) | |
| • System class | |
| IJob의 Excute 함수에 정의된 동작을 스케줄러에 병렬 스레드로 등록합니다. | |
| 이때 필요하다면 | |
| 같은 로직을 MonoBehaviour로도 구현 | • 의도 |
| 같은 하드웨어 환경에서 MonoBehaviour의 퍼포먼스를 ECS의 퍼포먼스와 직접 비교하기 위함입니다. | |
| MonoBehaviour의 경우, stack 기반의 오브젝트 풀링을 통해 최적화 했습니다. 오브젝트의 수가 만 단위를 넘어가기 떄문에 stack을 통한 지역성 및 캐시 적중률의 이점이 조금이라도 있을 것이라고 생각했습니다. | |
| MonoBehaviour 기반의 구현 과정은 생략하겠습니다. |
객체지향 방식의 풀링(pooling) 시도와 ECS 메모리 제약에 대한 이해
| 문제 상황 | • 의도 및 에러 발생 잦은 엔티티 생성/파괴로 인한 오버헤드를 줄이고자, 기존 OOP 방식처럼 Spawner 객체 내부에 Stack 자료구조를 두어 오브젝트 풀링을 시도했습니다. 그러나 컴파일 에러가 발생하며 사용할 수 없었습니다 | | --- | --- | | 원인 분석 | • unmanaged constraint 공식 문서와 레퍼런스를 확인한 결과, 유니티 ECS의 IComponentData는 가비지 컬렉터의 관리를 받는 Managed Type(C# 클래스, 일반 컬렉션 등)은 사용할 수 없는 Unmanaged 제약이 있습니다. | | 해결 및 깨달음 | • 일반적인 Stack 대신 ECS에 맞는 풀링 방식을 고려해야 합니다 근본적으로 생성과 파괴가 ECS에서 병목을 일으키는지부터 짚어야 합니다. 병목을 일으키는 주 원인은 대량의 Entity이며, spawner의 동작은 [BurstCompile] 옵션으로 충분합니다
• 데이터 지향에 대한 이해 및 사고방식 전환
ECS 자체가 이미 최적화된 구조이므로, 극단적인 병목이 아니면 전통적인 풀링 패턴을 고집할 이유가 없음을 깨달았습니다. |
멀티 스레딩 환경의 구조적 변경 제어 및 ECB
| 문제 상황 | • 병렬 스레딩 시도 Entity의 수명을 체크하고 수명이 지나면 파괴하는 작업을 최적화하기 위해 메인 스레드가 아닌 IJob 인터페이스를 사용하여 병렬 처리를 시도했습니다
• 에러 발생
멀티 스레드가 동작하는 Job 내부에서 DestroyEntity를 호출할 수 없음을 확인했습니다 | | --- | --- | | 원인 분석 | • 구조적 변경 불가 IJob 내부에서는 직접 DestroyEntity를 호출하면 메모리 레이아웃이 뒤틀리는 구조적 변경 에러 및 동기화 문제가 발생합니다. | | 해결 및 깨달음 | • ECB (Entity Command Buffer) 즉각적인 파괴 대신, ECB를 통해 접근할 수 있습니다. 정확히는 ECB는 명령을 미리 기록해두는 버퍼입니다. 이후 메인 스레드의 안전한 타이밍에 스스로 기록된 명령을 일괄처리 할 수 있도록 변경했습니다
• 깨달음
이 과정에서 유니티 ECS의 메모리 청크 관리와 스레드 동기화 개념을 배웠습니다. 멀티 스레딩 환경은 Critical Section과 Syncronization 등 사람이 직접 접근하기에는 복잡한 문제가 많습니다. 따라서 인터페이스 및 구조체에서 정의된 안전한 접근을 따라야 합니다. |
직접 코드를 읽고 작성하는 과정에서 손에 ECS 기반의 코드를 작성하는 감각을 익혔습니다. 또한, 직접 작성하며 놓친 부분과 오류가 발생하는 이유에 대해 파고 들면서 데이터 지향에 대해 이해하고 Unity ECS의 low level 동작에 대해서도 배울 수 있었습니다.
직접 기존 Mono와 ECS의 성능을 극단적인 조건에서 비교하는 과정에서 Mono 기반으로 구현한 씬에서 오브젝트 락이 발생하는 것을 확인했고, 최적화 측면에서 데이터 지향 프로그래밍의 필요성을 체감했습니다. 무엇보다 엔티티 숫자에 대한 걱정 없이 표현의 자유가 생긴다는 점에서 유저에게 새로운 즐거움을 줄 수 있을 것 같아 더 의미 있다고 느꼈습니다.
[ECS/DOTS #1] Unity ECS - Entity Component System 베이직 튜토리얼 [Best Tips & Tricks by Unity Japan]