Index speed
See original GitHub issueIn cookbook and gitter chat describes approach not use direct links in components like this
class SomeStorage : IComponent {
public GameEntity SomeEntity;
}
And use Index something like
public static class UniqIdIndex
{
public const string IndexKey = "IndexUniqId";
public static void AddUniqIndex(this GameContext contexts)
{
var positionIndex = new EntityIndex<GameEntity, int>(
IndexKey,
contexts.GetGroup(GameMatcher.UniqId),
(e, c) =>
{
var component = c as UniqIdComponent;
return component != null
? (int)component.Id
: (int)e.uniqId.Id;
}
);
contexts.AddEntityIndex(positionIndex);
}
public static HashSet<GameEntity> GetsByUniqId(this GameContext context, int id)
{
var index = (EntityIndex<GameEntity, int>)context.GetEntityIndex(IndexKey);
//var nodes = ;
return index.GetEntities(id);
}
public static GameEntity GetByUniqId(this GameContext context, int id)
{
var nodes = context.GetsByUniqId(id);
if (nodes.Count == 0)
return null;
return nodes.First();
}
}
Using an index especially for entities with many components is good. You may create custom lnt64 index for 4-8 components. But using an index for a component with a unique id seems to me a bad idea. Because of performance issues.
I write performance tests.(Win10x64 Ryzen 1700 8cores 64gb ram) Results:
Running performance tests… Name/Iterations/Speed/Mem PerfomanceTests.GetEntyAsRef: 1000000 3 ms 8 kb PerfomanceTests.GetEntyFromArray: 1000000 5 ms 164 kb PerfomanceTests.GetEntyFromFullArray: 1000000 5 ms 13467 kb PerfomanceTests.GetEntyFromFullDict: 1000000 16 ms 14728 kb PerfomanceTests.GetEntyFromIndex: 1000000 171 ms 19423 kb PerfomanceTests.GetEntyFromIndexNoFirst: 1000000 101 ms 19735 kb PerfomanceTests.GetComponentSpeed: 1000000 19 ms 16 kb PerfomanceTests.HasComponentSpeed: 1000000 12 ms 8 kb
Code of test
using System.Collections.Generic;
using ArcanoidWars.Logic;
using ArcanoidWars.Logic.Phisics;
using Entitas;
namespace PerfomanceTests
{
class SomeStorage
{
public GameEntity SomeEntity;
}
public class GetEntyAsRef : IPerformanceTest
{
int _iterations = 1000000;
private SomeStorage storage;
public void Before(int iterations)
{
_iterations = iterations;
storage = new SomeStorage();
storage.SomeEntity = new GameEntity();
}
public void Run()
{
for (int i = 0; i < _iterations; i++)
{
var e = storage.SomeEntity;
}
}
}
public class GetEntyFromArray : IPerformanceTest
{
int _iterations = 1000000;
private GameEntity[] _data;
public void Before(int iterations)
{
_iterations = iterations;
_data = new GameEntity[10000];
}
public void Run()
{
for (int i = 0; i < _iterations; i++)
{
var e = _data[i % 10000];
}
}
}
public class GetEntyFromFullDict : IPerformanceTest
{
int _iterations = 1000000;
private GameContext context;
private Dictionary<int, GameEntity> _data;
public void Before(int iterations)
{
_iterations = iterations;
context = new GameContext();
_data = new Dictionary<int, GameEntity>();
for (var i = 0; i < 10000; i++)
{
var e = context.CreateEntity();
e.AddUnitName(UnitNames.Solder1);
e.AddRadius(new Fix32(1));
e.AddPlayerOwn(PlayerNames.Player0);
_data[i] = e;
}
}
public void Run()
{
for (int i = 0; i < _iterations; i++)
{
var e = _data[i % 10000];
}
}
}
public class GetEntyFromFullArray : IPerformanceTest
{
int _iterations = 1000000;
private GameContext context;
private GameEntity[] _data;
public void Before(int iterations)
{
_iterations = iterations;
context = new GameContext();
_data = new GameEntity[10000];
for (var i = 0; i < 10000; i++)
{
var e = context.CreateEntity();
e.AddUnitName(UnitNames.Solder1);
e.AddRadius(new Fix32(1));
e.AddPlayerOwn(PlayerNames.Player0);
_data[i] = e;
}
}
public void Run()
{
for (int i = 0; i < _iterations; i++)
{
var e = _data[i % 10000];
}
}
}
public class GetEntyFromIndex : IPerformanceTest
{
int _iterations = 1000000;
private GameEntity[] _data;
private GameContext context;
private int _uniqIdIndex;
public void Before(int iterations)
{
_iterations = iterations;
context = new GameContext();
context.OnEntityCreated += AddUniqId;
context.AddUniqIndex();
for (var i = 0; i < 10000; i++)
{
var e = context.CreateEntity();
e.AddUnitName(UnitNames.Solder1);
e.AddRadius(new Fix32(1));
e.AddPlayerOwn(PlayerNames.Player0);
}
}
public void Run()
{
for (int i = 0; i < _iterations; i++)
{
//var e = _data[i % 10000];
context.GetByUniqId(i % 10000);
}
}
private void AddUniqId(IContext context, IEntity entity)
{
var e = (GameEntity)entity;
//FastIndex[_uniqIdIndex] = e;
e.AddUniqId(_uniqIdIndex);
_uniqIdIndex++;
}
}
public class GetEntyFromIndexNoFirst : IPerformanceTest
{
int _iterations = 1000000;
private GameEntity[] _data;
private GameContext context;
private int _uniqIdIndex;
public void Before(int iterations)
{
_iterations = iterations;
context = new GameContext();
context.OnEntityCreated += AddUniqId;
context.AddUniqIndex();
for (var i = 0; i < 10000; i++)
{
var e = context.CreateEntity();
e.AddUnitName(UnitNames.Solder1);
e.AddRadius(new Fix32(1));
e.AddPlayerOwn(PlayerNames.Player0);
}
}
public void Run()
{
for (int i = 0; i < _iterations; i++)
{
//var e = _data[i % 10000];
foreach (var e in context.GetsByUniqId(i % 10000))
{
}
}
}
private void AddUniqId(IContext context, IEntity entity)
{
var e = (GameEntity)entity;
//FastIndex[_uniqIdIndex] = e;
e.AddUniqId(_uniqIdIndex);
_uniqIdIndex++;
}
}
public class GetComponentSpeed : IPerformanceTest
{
int _iterations = 1000000;
private GameEntity _entity;
public void Before(int iterations)
{
_iterations = iterations;
var context = new GameContext();
_entity = context.CreateEntity();
_entity.AddUnitName(UnitNames.Solder1);
_entity.AddRadius(new Fix32(1));
_entity.AddPlayerOwn(PlayerNames.Player0);
}
public void Run()
{
for (int i = 0; i < _iterations; i++)
{
var c = _entity.playerOwn;
}
}
}
public class HasComponentSpeed : IPerformanceTest
{
int _iterations = 1000000;
private GameEntity _entity;
public void Before(int iterations)
{
_iterations = iterations;
var context = new GameContext();
_entity = context.CreateEntity();
_entity.AddUnitName(UnitNames.Solder1);
_entity.AddRadius(new Fix32(1));
_entity.AddPlayerOwn(PlayerNames.Player0);
}
public void Run()
{
for (int i = 0; i < _iterations; i++)
{
if (_entity.hasPlayerOwn)
{
}
}
}
}
Issue Analytics
- State:
- Created 6 years ago
- Comments:12 (6 by maintainers)

Top Related StackOverflow Question
@IDNoise I create test for PrimeIndex
PerfomanceTests.GetEntyFromFullArray: 1000000 27 ms 13660 kb PerfomanceTests.GetEntyFromFullDict: 1000000 34 ms 14842 kb PerfomanceTests.GetEntyFromIndex: 1000000 184 ms 19174 kb PerfomanceTests.GetEntyFromIndexNoFirst: 1000000 155 ms 20070 kb PerfomanceTests.GetEntyByPrimeIndex: 1000000 30 ms 13452 kb
I think in most cases use PrimeIndex is good approach.
What i ment is there is already PrimaryEntityIndex with 1 to 1 support (Dict<Key, Entity> and not <Key, HashSet<Entity> as in EntityIndex). You are not required to use entity index at all. You need to select best approach, if single dict get is too slow for you - write own solution, it can be custom index or separate solution. There is no tools that do all jobs good 😃 For me basic primary index was good enough (stored in field) and i had 30+ entities created\removed per frame with ~300 entities that were used in collisions (ships and projectiles) + 300 entities for other stuff.