Home

Awesome

Benchmark-CI Benchmark Archive

ECS.CSharp.Benchmark - Common use-cases

Motivation of this benchmark project:

See comments about this benchmark at reddit announcement post

Contents

<br/>

Tested projects

Projects ordered by dll size

ECSECS typeEntity๐Ÿ”’ C#size kbtestednuget latest
Leopotam.EcsLiteSparse Setintโœ…181.0.1nuget โฝยนโพ
Morpeh?class762023.1.0nuget
fennecsArchetypestruct1570.5.10-betanuget
DefaultEcsSparse Setstruct1690.18.0-beta01nuget
Friflo.Engine.ECSArchetypestructโœ…3073.0.0-preview.10nuget
Friflo.Engine.ECS.Boost๐Ÿ“ฆ+123.0.0-preview.10nuget
TinyEcsArchetypestruct4871.4.0nuget
Myriad.ECSArchetypestruct63121.2.1nuget
ArchArchetypestruct7651.2.8nuget
Arch.Relationships๐Ÿ“ฆโœ…+91.0.1nuget
Flecs.NETArch/Sparsestruct16004.0.0nuget

โฝยนโพ nuget package not published by project owner

ECS implementation

The are typically two types used by many ECS projects

Archetype

Entities are stored in single array. Components as stored in "tables" aka Archetypes.
An Archetype contains arrays of components for a specific set of component types.

Pros: Enable fast iteration of component queries.
Cons: Add/remove operations require a structural change.

Sparse Set

A sparse Set based ECS stores each component in its own sparse set which is has the entity id as key.

Pros: Fast add/remove operations.
Cons: Each component type requires an array with the size of all components.

<br/>

Benchmarks

Feature Matrix

ECSBasicRelationsCommand BufferEventsSearchWatch
Arch + Relationshipsโœ…โœ…โœ…1โœ…
DefaultEcsโœ…โœ…โœ…โœ…
fennecsโœ…โœ…2โœ…
Flecs.NETโœ…โœ…โœ…โœ…
Friflo.Engine.ECSโœ…โœ… ยนโœ…โœ…โœ…โœ…
Leopotam.EcsLiteโœ…
Morpehโœ…โœ…
Myriad.ECSโœ…โœ…
TinyEcsโœ…โœ…โœ…โœ…

ยน Ensures a cycle free entity hierarchy. See CheckTreeCycles()

<br/>
Benchmark DescriptionCategory
Basic
Add & Remove 1/5 components on 100 entitiesAddRemoveComponents
Create 100 entities with 1/3 componentsCreateEntity
Create WorldCreateWorld
Delete 100.000 entities with 5 componentsDeleteEntity
Get & Set 1 / 5 components on 100 entitiesGetSetComponents
Query 100 / 100.000 entities with 1 / 5 componentsQueryComponents
Relations
Add & Remove 1/100 link relation on 100 entitiesAddRemoveLinks
Add & Remove 1/10 relations on 100 entitiesAddRemoveRelations
Add & Remove 10 child entities on 100 parent entitiesChildEntitiesAddRemove
Command Buffer - deferred operations
Add & Remove 2 components on 100 entitiesCommandBufferAddRemove
Events - reactive ECS
Get event callback on Add & Remove 1 componentComponentEvents
Search
Search component field in 1.000.000 entitiesSearchComponentField
Search range of component fields in 1.000.000 entitiesSearchRange
<br/>

Run benchmarks always on an Apple Mac Mini M2.
Its hardware specs are fixed and can be compared with benchmarks you run by your own on this machine.

BenchmarkDotNet v0.13.12, macOS Sonoma 14.5 (23F79) [Darwin 23.5.0]
Apple M2, 1 CPU, 8 logical and 8 physical cores
.NET SDK 8.0.100
  [Host]   : .NET 8.0.0 (8.0.23.53103), Arm64 RyuJIT AdvSIMD
  ShortRun : .NET 8.0.0 (8.0.23.53103), Arm64 RyuJIT AdvSIMD

Overview

Readme benchmark update: 2024-08-09 using this run

Ratio: Average Performance Ratio - see C# ECS Benchmark Overview โ‹… Google Sheets

frifloFlecs.NETTinyEcsArchfennecsLeopotamDefaultEcsMorpeh
Ratio1.002.553.426.9619.022.573.8121.09
Notes333

Basic

Add & Remove 1 / 5 component on 100 entities

Note: See impact of structural changes in Archetype based ECS projects.

NamespaceEntitiesComponentsMeanRatioAllocated
Leopotam.EcsLite1001981 ns0.17-
DefaultEcs10011,431 ns0.25-
Scellecs.Morpeh10011,676 ns0.29-
Flecs.NET10013,761 ns0.65-
TinyEcs10013,984 ns0.69-
Friflo.Engine.ECS10015,787 ns1.00-
Arch10018,438 ns1.4612000 B
Myriad100119,044 ns3.29-
fennecs100138,594 ns6.6786400 B
Scellecs.Morpeh10054,988 ns0.64-
Leopotam.EcsLite10055,118 ns0.66-
DefaultEcs10057,120 ns0.92-
Friflo.Engine.ECS10057,779 ns1.00-
Arch100523,106 ns2.978800 B
TinyEcs100529,867 ns3.84-
Flecs.NET100537,030 ns4.76-
Myriad100554,960 ns7.06-
fennecs1005307,458 ns39.52620800 B

Create 100 entities with 1 / 3 components

Created entities are initialized with custom values as recommended here.
"in a real-world environment creating a large number of default entities isn't very useful and you'll always want to set component values!"

NamespaceEntitiesComponentsMeanRatioAllocated
Leopotam.EcsLite10011,374 ns0.307048 B
DefaultEcs10013,894 ns0.8512960 B
Friflo.Engine.ECS10014,574 ns1.0011600 B
TinyEcs10015,536 ns1.2286264 B
Arch10017,948 ns1.7436400 B
Flecs.NET10018,558 ns1.87736 B
Myriad100111,178 ns2.4518936 B
Scellecs.Morpeh100113,511 ns2.9942896 B
fennecs100137,324 ns8.22252376 B
Leopotam.EcsLite10033,163 ns0.8219672 B
Friflo.Engine.ECS10033,877 ns1.0015856 B
Arch10036,384 ns1.6528256 B
DefaultEcs10037,615 ns1.9523368 B
TinyEcs10038,953 ns2.31121896 B
Scellecs.Morpeh100316,683 ns4.2854400 B
Flecs.NET100317,391 ns4.55736 B
Myriad100321,014 ns5.3927288 B
fennecs1003103,193 ns26.41395856 B

Some frameworks support bulk creation of entities.
If available bulk creation is faster than the approach creating entities ony by one.

NamespaceEntitiesComponentsMeanRatioAllocated
Friflo.Engine.ECS1001988 ns1.007856 B
Arch10017,360 ns7.5336736 B
fennecs100119,065 ns19.48210712 B
Friflo.Engine.ECS10031,605 ns1.0012112 B
Arch10037,906 ns4.8828592 B
fennecs100325,458 ns15.74214448 B

Create World

NamespaceMeanRatioAllocated
DefaultEcs74 ns0.31336 B
Friflo.Engine.ECS238 ns1.003952 B
Myriad802 ns3.3619776 B
Leopotam.EcsLite1,443 ns6.0458944 B
Arch3,369 ns14.1137040 B
Scellecs.Morpeh4,295 ns17.985056 B
fennecs21,295 ns89.16169364 B
TinyEcs51,588 ns215.981312184 B
Flecs.NET1,033,699 ns4,265.551009 B

Delete 100.000 entities with 5 components

NamespaceEntitiesComponentsMeanRatioAllocated
Friflo.Engine.ECS10000051,590,691 ns1.00736 B
Myriad10000051,917,754 ns1.204196080 B
Flecs.NET10000051,985,879 ns1.25736 B
Arch10000052,899,945 ns1.733088 B
TinyEcs10000053,382,786 ns2.131480 B
DefaultEcs10000053,684,181 ns2.313200736 B
Leopotam.EcsLite10000054,814,687 ns3.036268768 B
fennecs10000055,720,832 ns3.604366912 B
Scellecs.Morpeh10000058,842,465 ns5.551398360 B

Get & Set 1 / 5 component on 100 entities

Note: Sparse Set based ECS projects are in lead because of viewer array lookups.

NamespaceEntitiesComponentsMeanRatioAllocated
Leopotam.EcsLite100165 ns0.32-
DefaultEcs1001115 ns0.57-
Scellecs.Morpeh1001141 ns0.70-
Friflo.Engine.ECS1001202 ns1.00-
Arch1001288 ns1.42-
Myriad1001346 ns1.71-
Flecs.NET1001582 ns2.87-
TinyEcs1001729 ns3.60-
fennecs10012,369 ns11.68-
Leopotam.EcsLite1005312 ns0.76-
Friflo.Engine.ECS1005410 ns1.00-
DefaultEcs1005457 ns1.11-
Scellecs.Morpeh1005614 ns1.50-
Arch10051,586 ns3.86-
Myriad10051,744 ns4.24-
Flecs.NET10052,690 ns6.55-
TinyEcs10054,004 ns9.74-
fennecs100514,345 ns34.91-

Query 100 / 100.000 entities with 1 / 5 components

Note: Archetype based ECS projects are in lead if querying multiple components.
Returned components are sequentially stored in memory providing a high cache hit rate.

Rule: Query implementations must not use vectorization (SIMD instructions).
If doing so this case requires a new benchmark category: QueryVectorization.

NamespaceEntitiesComponentsMeanRatioAllocated
DefaultEcs100144 ns0.86-
Friflo.Engine.ECS100151 ns1.00-
Myriad100165 ns1.28-
TinyEcs100167 ns1.32-
Leopotam.EcsLite100177 ns1.51-
Flecs.NET1001141 ns2.75-
fennecs1001203 ns3.9788 B
Arch1001299 ns5.84-
Scellecs.Morpeh1001374 ns7.30-
Friflo.Engine.ECS100565 ns1.00-
Myriad100571 ns1.09-
TinyEcs1005127 ns1.95-
Flecs.NET1005200 ns3.07-
DefaultEcs1005272 ns4.17-
Leopotam.EcsLite1005340 ns5.21-
Arch1005341 ns5.22-
fennecs1005441 ns6.7688 B
Scellecs.Morpeh1005782 ns11.99-
DefaultEcs100000145,890 ns0.94-
Friflo.Engine.ECS100000148,631 ns1.00-
fennecs100000149,366 ns1.0288 B
Flecs.NET100000149,899 ns1.03-
TinyEcs100000150,214 ns1.03-
Myriad100000151,355 ns1.06-
Leopotam.EcsLite100000177,551 ns1.60-
Arch1000001211,165 ns4.34-
Scellecs.Morpeh1000001855,866 ns17.601 B
Friflo.Engine.ECS100000548,055 ns1.00-
Myriad100000556,872 ns1.18-
Flecs.NET100000577,777 ns1.62-
TinyEcs100000589,260 ns1.86-
fennecs1000005108,646 ns2.2688 B
Arch1000005229,269 ns4.77-
DefaultEcs1000005318,011 ns6.62-
Leopotam.EcsLite1000005350,145 ns7.29-
Scellecs.Morpeh10000051,979,985 ns41.201 B

Query performance comparison: C++ vs C#

Performance reference vanilla C++ implementation

OSCPULang.Impl.EntitiesComponentsMean
macOSM2C++vanilla100000520,557 ns
macOSM2C#Friflo.Engine.ECS100000548,055 ns
Windowsx64C++vanilla100000558,430 ns
Windowsx64C#Friflo.Engine.ECS100000579,058 ns
<br/>

Relations

Some ECS projects have support for Entity Relationships.
Compared to relational databases: Entity relationships are similar to foreign keys referencing primary keys in other tables. ECS implementations typically ensure referential integrity. This means there are never links to entities which doesn't exist.

Relations enable directed links between entities aka entity relationships.
Directed link means that a link points from a source entity to a target entity.
A single entity can have multiple links to other target entities.

Add & Remove 1 / 100 link relations on 100 entities

NamespaceEntitiesRelationsMeanRatioAllocated
Friflo.Engine.ECS10015,912 ns1.00-
Flecs.NET100110,552 ns1.78-
TinyEcs100115,864 ns2.68-
Arch100172,628 ns12.2836800 B
fennecs100194,414 ns15.97180000 B
Flecs.NET100100976,119 ns0.781 B
Friflo.Engine.ECS1001001,252,632 ns1.001 B
Arch1001004,353,920 ns3.482180006 B
TinyEcs1001004,694,910 ns3.758 B
fennecs10010071,847,234 ns57.3593124905 B
<br/>

When dealing with an ECS following question arises at some point:
"Is it okay for performance to use an array, List<> or Dictionary<> as a component field"?
No, its not ๐Ÿ˜ฒ. Now each component has a one or more reference types.
As a result there is no cache locality anymore and GC requires much more CPU & memory resources.
This is the reason why many ECS projects have relations.

A typical limitation of an ECS is that an entity can only contain one component of a certain type.
Relations can be used to add multiple components of the same type to a single entity.
To differentiate relations added to the same entity following mechanisms are used:

Add & Remove 1 / 100 relations on 100 entities

NamespaceEntitiesRelationsMeanRatioAllocated
Friflo.Engine.ECS10013,761 ns1.00-
Flecs.NET100112,266 ns3.26-
TinyEcs100123,416 ns6.22-
Arch100148,360 ns12.8636800 B
fennecs100196,676 ns25.71180000 B
Friflo.Engine.ECS1001047,569 ns1.00-
Flecs.NET10010158,925 ns3.34-
Arch10010204,024 ns4.29240800 B
TinyEcs10010282,603 ns5.941 B
fennecs100101,578,632 ns33.202568001 B

Add & Remove 10 child entities on 100 parent entities

Child / parent entity relationships are used to build a hierarchy / tree of entities.
It is, among other things, a use case for scene trees, entity parenting or character rig skeletons.

NamespaceEntitiesMeanRatioAllocated
Friflo.Engine.ECS10025,754 ns1.00-
Flecs.NET10094,588 ns3.67-
TinyEcs100191,267 ns7.43-
Arch100433,217 ns16.82232801 B
fennecs100929,259 ns36.081800001 B
<br/>

Command buffer

A command buffer is used to record entity changes in a buffer.
While recording the state of entities remains unchanged.
These changes are applied to these entities when calling either
Playback(), Execute(), Commit() or DeferEnd()

Add & Remove 2 components on 100 entities using a command buffer

  1. Add components. Apply changes.
  2. Remove components. Apply changes.
NamespaceEntitiesMeanRatioAllocated
Scellecs.Morpeh1005,020 ns0.58-
Friflo.Engine.ECS1008,665 ns1.00-
TinyEcs10012,907 ns1.494800 B
Flecs.NET10014,344 ns1.65-
DefaultEcs10016,319 ns1.88-
Myriad10027,799 ns3.21-
Arch10048,811 ns5.634800 B
<br/>

Events

ECS implementations supporting callbacks for specific events are called reactive.
Typical event types are:

Get callback event on Add & Remove 1 component on 100 entities

NamespaceEntitiesMeanRatioAllocated
DefaultEcs1002,605 ns0.33-
TinyEcs1004,395 ns0.56-
Friflo.Engine.ECS1007,905 ns1.00-
Flecs.NET10011,251 ns1.42-
<br/>

Search

A search can be used to get all entities with a specific component field value.
This type of search is typically executed in O(1) .
E.g. to find all entities having a Player component where Player.name == "Bob"

struct Player { string name; } 

A search can also be used for range queries to find all entities with a component field value in a [min, max] range.
E.g. a range query return all entities with a Health component where Health.value is between 10 and 100.

struct Health { int value; } 

Search and Range Queries of component fields are explained at this Wiki.

Search component field in 1.000.000 entities

Execute 1000 searches for different search values in a data set of 1.000.000 entities.
Each result has 1 match.

NamespaceEntitiesMeanRatioAllocated
Friflo.Engine.ECS10000004,745 ns1.00-

Search range of component fields in 1.000.000 entities

Execute 1000 range queries with different [min, max] in a data set of 1.000.000 entities.
Each result has 100 matches.

NamespaceEntitiesMeanRatioAllocated
Friflo.Engine.ECS10000001,512,413 ns1.00560001 B
<br/>

Setup

The benchmark project can be build and executed on Windows, macOS & Linux.
All popular IDE's can be used to run and debug the project: Rider, Visual Studio Code & Visual Studio.

Benchmark constraints

The benchmarks CreateEntity and DeleteEntity are changing the state of World which has influence on the benchmark measurement.
If executing their [Benchmark] method multiple times the number of entities will grow / shrink for each method iteration.
This would slow down the execution over time and give wrong measurement results.
To avoid this these benchmarks are executed with 100.000 entities every time on a new World instance.

Contribution

Contributions are welcome.
Only requirement: Ensure it compiles.

Benchmark CLI

Currently ~ 100 benchmarks

The published benchmarks are executed without: --job argument.
The measurement difference when using --job Short were 2x in some benchmarks.

For documentation of --job argument see BenchmarkDotNet CLI args

Windows CLI

cd ./src

dotnet run -c Release --filter *                                # run all benchmarks
dotnet run -c Release --filter *AddRemoveComponents_Friflo*     # run a specific benchmark
dotnet run -c Release --filter *AddRemoveComponents*            # run benchmarks of single category
dotnet run -c Release --filter *Friflo*                         # run benchmarks of single project
dotnet run -c Release --filter *Friflo* *Arch*                  # compare benchmarks of two projects

# run basic benchmarks
dotnet run -c Release --filter *AddRemoveComponents* *GetSetComponents* *CreateEntity* *CreateWorld* *DeleteEntity* *Query*
dotnet run -c Release --filter *Links* *Relations*              # run relation benchmarks
dotnet run -c Release --filter *CommandBuffer*                  # run command buffer benchmarks
dotnet run -c Release --filter *Events*                         # run component events benchmarks
dotnet run -c Release --filter *Search*                         # run search benchmarks

macOS / Linux CLI

cd ./src

dotnet run -c Release --filter \*                               # run all benchmarks
dotnet run -c Release --filter \*AddRemoveComponents_Friflo\*   # run a specific benchmark
dotnet run -c Release --filter \*AddRemoveComponents\*          # run benchmarks of single category
dotnet run -c Release --filter \*Friflo\*                       # run benchmarks of single project
dotnet run -c Release --filter \*Friflo\* \*Arch\*              # compare benchmarks of two projects

# run basic benchmarks
dotnet run -c Release --filter \*AddRemoveComponents\* \*GetSetComponents\* \*CreateEntity\* \*CreateWorld\* \*DeleteEntity\* \*Query\*
dotnet run -c Release --filter \*Links\* \*Relations\*          # run relation benchmarks
dotnet run -c Release --filter \*CommandBuffer\*                # run command buffer benchmarks
dotnet run -c Release --filter \*Events\*                       # run component events benchmarks
dotnet run -c Release --filter \*Search\*                       # run search benchmarks

Other C# ECS Benchmarks

Footnotes

  1. Arch: Support for events requires a custom build. Performance of component related benchmarks will decrease. โ†ฉ

  2. fennecs: Support for events is planned according to its project README. โ†ฉ

  3. Sparse Set based ECS projects. โ†ฉ โ†ฉ2 โ†ฉ3