Detailed explanation of the principle of Unity ECS memory allocator

Why is ECS efficient and has good performance? The memory layout and allocation of Entity is a very important part. Today we will analyze how to make an efficient memory allocator in the Unity ECS architecture. This kind of thinking can also provide us with a good idea for memory allocation.

right! There is a game development exchange group here , which gathers a group of zero-based beginners who love to learn Unity, and there are also some technical bigwigs who are engaged in Unity development. You are welcome to exchange and learn.

1: Some basic concepts in ECS 

There are several important concepts in the Unity ECS framework:

Entity, ComponentData, System,Archetype, EntityManager, World;

ComponentData:

Component data, when developing, you can put the data related to each function into ComponentData.

Entity:

Object entity, pure data object, which contains ComponentData one by one, we put the data used by each related function in the ComponentData of Entity, as shown in the figure:

(Schematic diagram of Entity memory object layout)

System:

The implementation of the specific algorithm logic, the System algorithm data is based on the corresponding processing ComponentData in the Entity. Every time the game engine is updated, the System will update the data according to the ComponentData it uses in each Entity to complete the calculation.

EntityManager:

Responsible for Entity related type information, creation and management of object entities.

Archetype:

Entity is composed of multiple ComponentData, each Entity type corresponds to an Archetype, which describes the Entity type and related layout. The Archetype is created by the EntityManager. After the creation, the size of the Entity of this type, the component data it contains, and the memory layout are all determined.

World:

The world contains multiple entities to form Entity groups, and these Entity groups are isolated through the world. Unity can have multiple different worlds at the same time, each world independently contains EntityManager, and Systems. An Entity created in the world belongs only to this world. Each System in the world can only iterate the Entity entity data in one world. You can create multiple worlds.

Summarize the use steps and logical relationship of Unity ECS:

(1) Create an ECS World;

(2) Use World's EntityManager to create Archetypes for different Entity types;

(3) Based on Archetype, we create the Entity memory object, and initialize each ComponentData data in the Entity;

(4) Write the System algorithm for ECS World, and World will call the System algorithm to iterate the relevant ComponentData in each Entity;

(Unity ECS Architecture Diagram)

2: ECS  efficient memory model and layout

As an architect, designing framework data structures is key. Especially for game development, dealing with iterating through hundreds or thousands of game objects. How to achieve efficient memory model and layout? First of all, we must understand how to layout memory efficiently. The efficient layout of the memory model is mainly considered from two aspects:

1: Memory alignment.

When the CPU accesses memory data at different addresses, if the memory address is based on 2^n data alignment (n depends on the CPU), the access is most efficient. For example, if we align with 16-byte data, then when the object memory starts to be arranged, it starts from the memory location whose address is divisible by 16. For efficient memory layout, the memory address of the object instance should be aligned, and the memory address of each data member in the object instance should also be aligned. Entity's memory allocation and memory layout. When creating an Entity, the ECS system will pay attention to the memory alignment of the Entity, and also pay attention to the memory alignment of the ComponentData in the Entity. If it is not aligned, it will be vacant. For example, 16-byte alignment, a ComponentData has only 14 bytes, in fact, when arranging the next ComponentData, it starts from 16 bytes.

2: When the CPU fetches the memory data through the address, the data arrangement and fetching speed will be faster.

ECS naturally has this advantage. All logic code iterations are based on ComponentData, that is, the data that the algorithm iteratively accesses and processes are all together in one ComponentData. Accessing data in this way is efficient and avoids address jumping back and forth.

3: Design of Entity Memory Allocator Chunk

The creation and destruction of a large number of entities and the cross-creation of a large number of different types of data objects can easily cause memory fragmentation. What is memory fragmentation? Let me give you an example, object A, object B (100 bytes), object C are arranged continuously, object B is destroyed, the memory is released, and at the same time, the allocation of object D (80 bytes) is requested. Object B allocates a piece of memory to object D, leaving a small piece of memory (20 bytes). And this small piece of memory cannot be allocated to any object in the current system. In this way, this small memory can no longer be used, resulting in memory fragmentation. As the system continues to run, with more and more creations and releases, Memory fragmentation is increasing and allocation/deallocation efficiency is getting lower and lower. Eventually the system gets slower and slower. To solve this massive creation and destruction, we generally use the Cache memory pool to do it. How is the memory pool designed in the Unity ECS architecture? Next, let's analyze the Chunk-based memory allocator in ECS. Different Archetypes correspond to different Entity memory. The system may have different Archetypes in N, and Archetypes are created by the user based on the combination of component data.

In summary, Unity ECS designs the memory allocation buffer pool as follows:

(1) Based on the fixed size Chunk to do the memory buffer pool. Each Chunk is 16KB in size (quoted from the UnityECS documentation)

(2) When the Archetype is determined, the memory size of the Entity is determined, and the current number of entities that can be allocated by each Chunk can be determined. When creating an Entity based on Archetype, first take a Chunk from the memory pool, and then allocate the Entity from the Chunk according to the number of entities that can be created in each Chunk. If the Chunk is allocated, take another Chunk from the chunk memory buffer pool. Since it is created based on the Archetype, all entities of this Archetype have the same memory size. When they are released, we can cache the Entity based on the Archetype and reuse the previous Entity.

For the OS, the ECS framework allocates and frees memory based on chunks, avoiding memory fragmentation (all of the same size). For ArcheType, based on the specific ArcheType's Entity to do memory caching, allocation and release are very efficient.

After reading the architecture design and memory layout of Unity ECS, we have a good reference for ECS architecture design (game server) elsewhere, and the design is inseparable from it.

Guess you like

Origin blog.csdn.net/voidinit/article/details/126497680