Skip to content

v3.0.0 - Performance And Plugins

Compare
Choose a tag to compare
@grofit grofit released this 18 Jan 14:02
· 207 commits to master since this release

Performance Changes - Structs, Refs, Batched Systems

Under the hood a lot of the layering and architecture from IEntity down to IComponentDatabase has been changed to use less memory, be more efficient and support structs. There is a lot of other changes but lets drill down into them a bit more.

struct support for components

Historically a component always used to be a class, which is fine for most people, and you can continue to use components this way without issue. However this comes with a performance overhead in terms of how its accessed and allocated.

Now that structs can be used it allows them to be allocated far quicker and accessed quicker, this also can provide MASSIVE performance benefits if you combine it with batching where you are trying to make better use of the CPU prefetch and cache to only access the bits you care about and just iterate over them.

public struct PositionComponent : IComponent
{
   public Vector3 Position;
}

More docs on this subject will be added shortly which will go into far more depth on the subject.

Component Database Changes

Without droning on too much, the database used to historically bulk allocate component memory based on the entity size, whereas now it will instead allocate what it needs and try re-using until it needs to expand, this will reduce memory usage and also makes it more efficient.

Building off the back of those changes we also now are able to expose the underlying component pools and arrays easier, which means you can actually grab them and manually iterate through them yourself if you want to get faster performance than querying each entity individually.

Getting components by ref

If you mainly use class based components this wont be of any interest to you, but to those who use struct based components you can get the components by ref which allows you to update it in place, and ripple those changes down into the underlying component. This can make things slightly quicker and more succinct without having to replace the component every time you change something.

// Additions to IEntity
ref T GetComponent<T>(int componentTypeId) where T : IComponent;        
ref T AddComponent<T>(int componentTypeId) where T : IComponent, new();

// Example use case
ref var positionComponent = ref entity.AddComponent<PositionComponent>(PositionComponentTypeId);

Batched Systems

Until now when you were dealing with groups and systems you would generally get given a load of entities and you would loop through them getting the components for each one and then doing your calculations on them. While this is fine for simple things, when have lots of entities and more components it can become a chore to get each component, and it is also slower to do as it needs to keep retrieving random bits of memory all over the place.

With the new BatchedSystem and ReferenceBatchedSystem types you are able to stipulate ahead of time what components you require for this system to operate and pre-fetch them all in one big chunk of memory. This makes performance FAAAAR quicker and in most cases makes it slightly easier to do your work, as you can just loop through each Batch (contains entity id and the components you need) and already have the components in memory ready to go, meaning less effort for the computer to resolve all your guff.

// example of typical component access
public void Process(IEntity entity)
{
    var basicComponent = entity.GetComponent<SomeComponent1>();
	basicComponent.Position += Vector3.One;
	basicComponent.Something += 10;

	var basicComponent2 = entity.GetComponent<SomeComponent2>();
	basicComponent2.Value += 10;
	basicComponent2.IsTrue = true;
}
// example of batched component access (showing structs, but reference types same without ref
)
public void Process(int entityId, ref SomeComponent1 basicComponent, ref SomeComponent2 basicComponent2)
{
	basicComponent.Position += Vector3.One;
	basicComponent.Something += 10;
	basicComponent2.Value += 10;
	basicComponent2.IsTrue = true;
}

As an example if you were to compare the 2 approaches you would get a drastic difference in time taken to process, there is an example bare bones scenario which does this (abiet simpler form) in the project it makes 200,000 entities with 2 components, and has some logic to mimic the system process, then loops 100 times.

  • Looping each entity and getting each component individually || 13s to complete
  • Looping through the batched version || 600ms to complete

This is on a potato laptop and goes to show that the batched approach is roughly 20x faster, and its very little extra effort, it also provides large performance boosts for class based components, just not as fast as structs.

One other benefit is behind the scenes the batches are managed for you, much like IObservableGroup instances, so if you have 5 systems sharing the same batches, they will all be using the same underlying batch behind the scenes which means each system isn't having to keep its own copy and maintain it.

Plugins

So plugins have existed for quite a while in EcsRx but all changes to EcsRx framework have happened within the core part. Going forward we have tried to split out more of the optional parts of the system into plugins. This allows you to decide if you want to use reactive systems, and this has also allowed the new batching process to be developed without impacting the core framework (as it requires unsafe code).

The first parts to be split into plugins are:

  • EcsRx.Views -> EcsRx.Plugins.Views
  • EcsRx.Systems -> EcsRx.Plugins.ReactiveSystems
  • EcsRx (computeds) -> EcsRx.Plugins.Computeds
  • EcsRx.Plugins.Batches (new)

WARNINGS!!!

This latest version makes use of the latest and greatest C# 7.3 language features, this is going to be a problem for some people, and for those people who are not able to adopt the latest C# version I would suggest sticking with the previous version until you can update.

Closing Blurbs

As part of these changes it paves the way to potentially have more performance increases going forward as well as improve the eco system in a simpler more isolated way using plugins. There is a lot of work that is still proposed for interacting with entities and observable groups (as this is still a slow part of the system), but this hopefully will give people more freedom and more flexibility to do what they want with the system.

These changes will hopefully be rolled into the Unity and Monogame versions shortly, and if anyone wants to help out we could really do with assistance with docs/example maintenance and creation.