44
\$\begingroup\$

Lately, I've been doing a lot of reading on entity systems to implement in my C++/OpenGL game engine. The two key benefits that I constantly hear lauded about entity systems are

  1. the easy construction of new kinds of entities, due to not having to tangle with complex inheritance hierarchies, and
  2. cache efficiency, which I'm having trouble understanding.

The theory is simple, of course; each component is stored contiguously in a block of memory, so the system that cares about that component can just iterate over the whole list, without having to jump around in memory and kill the cache. The problem is that I can't really think of a situation where this is actually practical.


First, let's look at how the components are stored, and how they reference each other. Systems need to be able to work with more than one component, i.e. both the rendering and physics system needs to access the transform component. I've seen a number of possible implementations that address this, and none of them do it well.

You can have components store pointers to other components, or pointers to entities that store pointers to components. However, as soon as you throw pointers into the mix, you're already killing cache efficiency. You can ensure that every component array is 'n' large, where 'n' is the number of entities alive in the system, but this approach is hideously wasteful of memory; this makes it very difficult to add new component types to the engine, but still throws away cache efficiency, because you're jumping from one array to the next. You could interleave your entity array, instead of keeping separate arrays, but you're still wasting memory; making it prohibitively expensive to add new components or systems, but now with the added benefit of invalidating all of your old levels and save files.

This is all assuming that entities are processed linearly in a list, every frame or tick. In reality, this is not often the case. Say you use a sector/portal renderer, or an octree, to perform occlusion culling. You might be able to store entities contiguously within a sector/node, but you're going to be jumping around, whether you like it or not. Then you have other systems, which might prefer entities stored in some other order. AI might be okay with storing entities in a big list, until you start working with AI LOD; then, you'll want to split that list up according to distance to the player, or some other LOD metric. Physics is going to want to use that octree. Scripts don't care, they need to run, no matter what.

I could see splitting components up between "logic" (e.g. ai, scripts, etc) and "world" (e.g. rendering, physics, audio, etc), and managing each list separately, but these lists still have to interact with each other. AI is pointless, if it can't affect the transform or animation state used for an entity's rendering.


How are entity systems "cache-efficient" in a real-world game engine? Perhaps there's a hybrid approach that everyone's using, but not talking about, like storing entities in an array globally and referencing it within the octree?

\$\endgroup\$
3
  • \$\begingroup\$ Please note nowadays you have multi-core CPU and cache bigger then one row. Even if you need access information from two systems, they are likely to fit in both. Also note, graphic rendering is often separated - exactly for what you stated (trees, scenes, ..) \$\endgroup\$
    – wondra
    Aug 16, 2014 at 21:54
  • 2
    \$\begingroup\$ Entity systems are not always cache-efficient, but it can be an advantage of some implementations (over other ways to accomplish similar things). \$\endgroup\$
    – user1430
    Aug 17, 2014 at 1:52
  • \$\begingroup\$ Related gamedev.stackexchange.com/q/61196/17516 \$\endgroup\$
    – danijar
    Aug 22, 2014 at 19:52

1 Answer 1

57
\$\begingroup\$

The two key benefits that I constantly hear lauded about entity systems are 1) the easy construction of new kinds of entities due to not having to tangle with complex inheritance hierarchies, and 2) cache efficiency.

Note that (1) is a benefit of component-based design, not just ES/ECS. You can use components in many ways that do not have the "systems" part and they work just fine (and plenty of both indie and AAA games use such architectures).

The standard Unity object model (using GameObject and MonoBehaviour objects) is not an ECS, but is component-based design. The newer Unity ECS feature is an actual ECS, of course.

Systems need to be able to work with more than one component, ie both the rendering and physics system need to access the transform component.

Some ECS sort their component containers by Entity ID, meaning that the corresponding components in each group will be in the same order.

This means that if you're linearly iterating over graphics component you're also linearly iterating over the corresponding transform components. You might be skipping some of the transforms (since you may have physics trigger volumes that you don't render or such) but since you're always skipping forward in memory (and by not particularly huge distances, usually) you're still going to have efficiency gains.

This is similar to how you have the Structure Of Arrays (SOA) being the recommended approach for HPC. The CPU and cache can deal with multiple linear arrays almost as well as it can deal with a single linear array, and far better than it can deal with random memory access.

Another strategy used in some ECS implementations - including Unity ECS - is to allocate Components based on the Archetype of their corresponding Entity. That is, all Entities with precisely the set of Components (PhysicsBody, Transform) will be allocated separately from the Entities with different Components (e.g. PhysicsBody, Transform, and Renderable).

Systems in such designs work by first finding all Archetypes that match their requirements (that have the required set of Components), iterating that list of Archetypes, and iterating the Components stored within each matching Archetype. This allows for completely linear and true O(1) component access within an Archetype and allows Systems to find compatible Entities with very low overhead (by searching a small list of Archetypes rather than searching potentially hundreds of thousands of Entities).

You can have components store pointers to other components, or pointers to entities that store pointers to components.

Components referencing other components on the same entity don't need to store anything. To reference components on other entities, just store the entity ID.

If a component is allowed to exist more than once for a single entity and you need to reference a particular instance, store the other entity's ID and a component index for that entity. Many ECS implementations do not allow this case, however, specifically because it makes these operations less efficient.

You can ensure that every component array is 'n' large, where 'n' is the number of entities alive in the system

Use handles (e.g. indices + generation markers) and not pointers and then you can resize the arrays without fear of breaking object references.

You can also use a "chunked array" approach (an array of arrays) similar to many common std::deque implementation (though without the pitifully-small chunk size of said implementations) if you want to allow pointers for some reason or if you have measured problems with array resize performance.

Secondly, this is all assuming that entities are processed linearly in a list every frame/tick, but in reality this is not often the case

It depends on the entity. Yes, for many use cases, it's not true. Indeed, this is why I so strongly stress the difference between component-based design (good) and entity-system (a specific form of CBD).

Some of your components will certainly be easy to process linearly. Even in normally "tree heavy" use cases we have definitely seen performance increases from using tightly packed arrays (mostly in cases involving an N of a few hundred at most, like AI agents in a typical game).

Some developers have also found that the performance advantages of using data-oriented linearly-allocated data structures outweighs the performance advantage of using "smarter" tree-based structures. It all depends on the game and specific use cases, of course.

Say you use a sector/portal renderer or an octree to perform occlusion culling. You might be able to store entities contiguously within a sector/node, but you're going to be jumping around whether you like it or not.

You'd be surprised how much the array still helps. You're jumping around in a much smaller region of memory than "anywhere" and even with all the jumping you're still much more likely to end up in something in cache. With a tree of a certain size or less, you might even be able to prefetch the whole thing into cache and not ever have a cache miss on that tree.

There are also tree structures that are built to live in tightly packed arrays. For instance, with your octree, you can use a heap-like structure (parents before children, siblings next to each other) and ensure that even when you "drill down" the tree you're always iterating forward in the array, which helps the CPU optimize the memory accesses / cache lookups.

Which is an important point to make. An x86 CPU is a complex beast. The CPU is effectively running a microcode optimizer on your machine code, breaking it into smaller microcode and reordering instructions, predicting memory access patterns, etc. Data access patterns matter more than may be readily apparent if all you have is a high-level understanding of how the CPU or cache work.

Then you have other systems, which might prefer entities stored in some other order.

You could store them multiple times. Once you strip down your arrays to the bare minimum details, you might find you actually save memory (since you've removed your 64-bit pointers and can use smaller indices) with this approach.

You could interleave your entity array instead of keeping separate arrays, but you're still wasting memory

This is antithetical to good cache usage. If all you care about are the transforms and graphics data, why make the machine spend time pulling in all that other data for physics and AI and input and debug and so on?

That's the point usually made in favor of ECS vs monolithic game objects (though not really applicable when comparing to other component-based architectures).

For what it's worth, most "production-grade" ECS implementations of which I'm aware use interleaved storage. The popular Archetype approach I mentioned earlier (used in Unity ECS, for example) is very explicitly build to use interleaved storage for Components associated with an Archetype.

AI is pointless if it can't affect the transform or animation state used for an entity's rendering.

Just because AI can't efficiently access transform data linearly doesn't mean that no other system can use that data layout optimization effectively. You can use a packed array for transform data without stopping game logic systems from doing things the ad hoc way game logic systems usually do things.

You're also forgetting code cache. When you use the systems approach of ECS (unlike some more naive component architecture) you're guaranteeing that you're running the same small loop of code and not jumping back and forth through virtual function tables to an assortment of random Update functions strewn all over your binary. So in the AI case, you really want to keep all your different AI components (because certainly you have more than one so that you can compose behaviors!) in separate buckets and process each list separately in order to get the best code cache usage.

With a delayed event queue (where a system generates a list of events but doesn't dispatch them until the system finishes processing all entities) you can ensure that your code cache is used well while keeping events.

Using an approach where each system knows which event queues to read out of for the frame, you can even make reading events fast. Or faster than without, at least.

Remember, performance isn't absolute. You don't need to eliminate every last single cache miss to start seeing the performance benefits of good data-oriented design.

There is still active research into making many game systems work better with ECS architecture and data-oriented design patterns. Similarly to some of the amazing things we've seen done with SIMD in recent years (e.g. JSON parsers), we're seeing more and more things done with ECS architecture that doesn't seem intuitive to classical game architectures but offers a number of benefits (speed, multi-threading, testability, etc.).

Or perhaps there's a hybrid approach that everyone's using but nobody's talking about

This is what I have advocated in the past, especially for folks who are skeptical of ECS architecture: use good data-oriented approaches to components where the performance is critical. Use simpler architecture where simplicity improves development time. Don't shoe-horn every single component into a strict over-definition of componentization like ECS proposes. Develop your component architecture in such a way that you can easily use ECS-like approaches where they make sense and use simpler component structure where ECS-like approach don't make sense (or make less sense than a tree structure, or so on).

I'm personally a relatively recent convert to the true power of ECS myself. Though for me, the deciding factor was something rarely mentioned about ECS: it makes writing tests for game systems and logic almost trivial compared to the tightly-coupled logic-laden component-based designs I've worked with in the past. Since ECS architectures put all logic in Systems, which just consume Components and produce Component updates, building a "mock" set of Components to test System behaviour is quite easy; because most game logic should live solely inside Systems, that effectively means that testing all your Systems will provide fairly high code coverage of your game logic. Systems can use mock dependencies (e.g. GPU interfaces) for tests with far less complexity or performance impact than you'd get if you had to inject those dependencies into thousands of interconnected logic-heavy Components.

As an aside, you might note that a lot of people talk about ECS without really understanding what it even is. I see classic Unity referred to as ECS with depressing frequency, illustrating that too many game developers equate "ECS" with "Components" and pretty much ignore the "Entity System" part entirely. You see a lot of love heaped on ECS on the Internet when a large portion of the people are really just advocating component-based design, not actual ECS. At this point it's almost pointless to argue it; ECS has been corrupted from its original meaning into a generic term and you might as well accept that "ECS" doesn't mean the same thing as "data-oriented ECS". :/

\$\endgroup\$
5
  • 1
    \$\begingroup\$ It would be useful to define (or link to) what you mean by ECS, if you're going to compare/contrast it with general component-based design. I for one am not clear on what the distinction is. :) \$\endgroup\$ Aug 17, 2014 at 3:39
  • \$\begingroup\$ Thanks a lot for the answer, it looks like I still have a lot of research to do on the subject. Are there any books you could point me to? \$\endgroup\$ Aug 18, 2014 at 4:46
  • 3
    \$\begingroup\$ @NathanReed: ECS is documented in places like entity-systems.wikidot.com/es-terminology . Component-based design is just regular ol' aggregation-over-inheritance but with the focus on the dynamic composition useful for game design. You can write component-based engines that don't use Systems or Entities (in ECS terminology's meaning) and you can use components for far more in a game engine than just game-objects/entities, which is why I stress the difference. \$\endgroup\$ Aug 18, 2014 at 9:51
  • 3
    \$\begingroup\$ This is one of the best posts about ECS's I've ever read, despite all the literature around the web. Mega thumbs up. So, Sean, ultimately, what's your general approach to develop games (rather the complex ones) ? A pure ECS? A mixed approach between component-based and ECS? I'd love to know more about your designs! Is it asking too much to get a hold of you in Skype or something else for discussing this? \$\endgroup\$
    – Grimshaw
    Dec 8, 2014 at 14:37
  • 2
    \$\begingroup\$ @Grimshaw: gamedev.net is a decent place for more open-ended discussion, as would be reddit.com/r/gamedev I suppose (though I'm not a redditer myself). I'm frequently on gamedev.net, as are many other bright people. I don't generally do one-on-one conversations; I'm pretty busy and prefer my downtime (i.e. compiling) to be spent helping the many rather than the few. :) \$\endgroup\$ Dec 8, 2014 at 20:39

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .