Entity fetch benchmark for experimenting with generational Entity UIDs.
This commit is contained in:
320
Content.Benchmarks/EntityFetchBenchmark.cs
Normal file
320
Content.Benchmarks/EntityFetchBenchmark.cs
Normal file
@@ -0,0 +1,320 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Benchmarks
|
||||
{
|
||||
[SimpleJob]
|
||||
public class EntityFetchBenchmark
|
||||
{
|
||||
[Params(1000)] public int N { get; set; }
|
||||
|
||||
public int M { get; set; } = 10;
|
||||
|
||||
private readonly DictEntityStorage _dictStorage = new DictEntityStorage();
|
||||
private readonly GenEntityStorage _genStorage = new GenEntityStorage();
|
||||
|
||||
private IEntityStorage<DictEntity, DictEntityUid> _dictStorageInterface;
|
||||
private IEntityStorage<GenEntity, GenEntityUid> _genStorageInterface;
|
||||
|
||||
private DictEntityUid[] _toReadDict;
|
||||
private DictEntity[] _toWriteDict;
|
||||
|
||||
private GenEntityUid[] _toReadGen;
|
||||
private GenEntity[] _toWriteGen;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_dictStorageInterface = _dictStorage;
|
||||
_genStorageInterface = _genStorage;
|
||||
|
||||
var r = new Random();
|
||||
|
||||
var allocatedGen = new List<GenEntity>();
|
||||
var allocatedDict = new List<DictEntity>();
|
||||
|
||||
for (var i = 0; i < N; i++)
|
||||
{
|
||||
allocatedGen.Add(_genStorage.NewEntity());
|
||||
allocatedDict.Add(_dictStorage.NewEntity());
|
||||
}
|
||||
|
||||
var delTo = N / 2;
|
||||
for (var i = 0; i < delTo; i++)
|
||||
{
|
||||
var index = r.Next(allocatedDict.Count);
|
||||
|
||||
var gEnt = allocatedGen[index];
|
||||
var dEnt = allocatedDict[index];
|
||||
|
||||
_genStorage.DeleteEntity(gEnt);
|
||||
_dictStorage.DeleteEntity(dEnt);
|
||||
|
||||
allocatedGen.RemoveSwap(i);
|
||||
allocatedDict.RemoveSwap(i);
|
||||
}
|
||||
|
||||
for (var i = 0; i < N; i++)
|
||||
{
|
||||
allocatedGen.Add(_genStorage.NewEntity());
|
||||
allocatedDict.Add(_dictStorage.NewEntity());
|
||||
}
|
||||
|
||||
for (var i = 0; i < delTo; i++)
|
||||
{
|
||||
var index = r.Next(allocatedDict.Count);
|
||||
|
||||
var gEnt = allocatedGen[index];
|
||||
var dEnt = allocatedDict[index];
|
||||
|
||||
_genStorage.DeleteEntity(gEnt);
|
||||
_dictStorage.DeleteEntity(dEnt);
|
||||
|
||||
allocatedGen.RemoveSwap(i);
|
||||
allocatedDict.RemoveSwap(i);
|
||||
}
|
||||
|
||||
_toReadDict = new DictEntityUid[M];
|
||||
_toWriteDict = new DictEntity[M];
|
||||
_toReadGen = new GenEntityUid[M];
|
||||
_toWriteGen = new GenEntity[M];
|
||||
|
||||
for (var i = 0; i < M; i++)
|
||||
{
|
||||
var index = r.Next(allocatedDict.Count);
|
||||
|
||||
_toReadDict[i] = allocatedDict[index].Uid;
|
||||
_toReadGen[i] = allocatedGen[index].Uid;
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void BenchGenId()
|
||||
{
|
||||
for (var i = 0; i < M; i++)
|
||||
{
|
||||
var uid = _toReadGen[i];
|
||||
if (_genStorage.TryGetEntity(uid, out var entity))
|
||||
{
|
||||
_toWriteGen[i] = entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void BenchDict()
|
||||
{
|
||||
for (var i = 0; i < M; i++)
|
||||
{
|
||||
var uid = _toReadDict[i];
|
||||
if (_dictStorage.TryGetEntity(uid, out var entity))
|
||||
{
|
||||
_toWriteDict[i] = entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void BenchGenIdInterface()
|
||||
{
|
||||
for (var i = 0; i < M; i++)
|
||||
{
|
||||
var uid = _toReadGen[i];
|
||||
if (_genStorageInterface.TryGetEntity(uid, out var entity))
|
||||
{
|
||||
_toWriteGen[i] = entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void BenchDictInterface()
|
||||
{
|
||||
for (var i = 0; i < M; i++)
|
||||
{
|
||||
var uid = _toReadDict[i];
|
||||
if (_dictStorageInterface.TryGetEntity(uid, out var entity))
|
||||
{
|
||||
_toWriteDict[i] = entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class DictEntityStorage : EntityStorage<DictEntity, DictEntityUid>
|
||||
{
|
||||
private int _nextValue;
|
||||
|
||||
private readonly Dictionary<DictEntityUid, DictEntity> _dict = new Dictionary<DictEntityUid, DictEntity>();
|
||||
|
||||
public override bool TryGetEntity(DictEntityUid entityUid, out DictEntity entity)
|
||||
{
|
||||
if (!_dict.TryGetValue(entityUid, out entity))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return !entity.Deleted;
|
||||
}
|
||||
|
||||
public DictEntity NewEntity()
|
||||
{
|
||||
var e = new DictEntity(new DictEntityUid(_nextValue++));
|
||||
_dict.Add(e.Uid, e);
|
||||
return e;
|
||||
}
|
||||
|
||||
public void DeleteEntity(DictEntity e)
|
||||
{
|
||||
DebugTools.Assert(!e.Deleted);
|
||||
|
||||
e.Deleted = true;
|
||||
|
||||
_dict.Remove(e.Uid);
|
||||
}
|
||||
}
|
||||
|
||||
private interface IEntityStorage<TEntity, TEntityUid>
|
||||
{
|
||||
public bool TryGetEntity(TEntityUid entityUid, out TEntity entity);
|
||||
}
|
||||
|
||||
private abstract class EntityStorage<TEntity, TEntityUid> : IEntityStorage<TEntity, TEntityUid>
|
||||
{
|
||||
public abstract bool TryGetEntity(TEntityUid entityUid, out TEntity entity);
|
||||
|
||||
public TEntity GetEntity(TEntityUid entityUid)
|
||||
{
|
||||
if (!TryGetEntity(entityUid, out var entity))
|
||||
{
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class GenEntityStorage : EntityStorage<GenEntity, GenEntityUid>
|
||||
{
|
||||
private (int generation, GenEntity entity)[] _entities = new (int, GenEntity)[1];
|
||||
private readonly List<int> _availableSlots = new List<int> {0};
|
||||
|
||||
public override bool TryGetEntity(GenEntityUid entityUid, out GenEntity entity)
|
||||
{
|
||||
var (generation, genEntity) = _entities[entityUid.Index];
|
||||
entity = genEntity;
|
||||
|
||||
return generation == entityUid.Generation;
|
||||
}
|
||||
|
||||
public GenEntity NewEntity()
|
||||
{
|
||||
if (_availableSlots.Count == 0)
|
||||
{
|
||||
// Reallocate
|
||||
var oldEntities = _entities;
|
||||
_entities = new (int, GenEntity)[_entities.Length * 2];
|
||||
oldEntities.CopyTo(_entities, 0);
|
||||
|
||||
for (var i = oldEntities.Length; i < _entities.Length; i++)
|
||||
{
|
||||
_availableSlots.Add(i);
|
||||
}
|
||||
}
|
||||
|
||||
var index = _availableSlots.Pop();
|
||||
ref var slot = ref _entities[index];
|
||||
var slotEntity = new GenEntity(new GenEntityUid(slot.generation, index));
|
||||
slot.entity = slotEntity;
|
||||
|
||||
return slotEntity;
|
||||
}
|
||||
|
||||
public void DeleteEntity(GenEntity e)
|
||||
{
|
||||
DebugTools.Assert(!e.Deleted);
|
||||
|
||||
e.Deleted = true;
|
||||
|
||||
ref var slot = ref _entities[e.Uid.Index];
|
||||
slot.entity = null;
|
||||
slot.generation += 1;
|
||||
|
||||
_availableSlots.Add(e.Uid.Index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private readonly struct DictEntityUid : IEquatable<DictEntityUid>
|
||||
{
|
||||
public readonly int Value;
|
||||
|
||||
public DictEntityUid(int value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public bool Equals(DictEntityUid other)
|
||||
{
|
||||
return Value == other.Value;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is DictEntityUid other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
public static bool operator ==(DictEntityUid left, DictEntityUid right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(DictEntityUid left, DictEntityUid right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct GenEntityUid
|
||||
{
|
||||
public readonly int Generation;
|
||||
public readonly int Index;
|
||||
|
||||
public GenEntityUid(int generation, int index)
|
||||
{
|
||||
Generation = generation;
|
||||
Index = index;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class DictEntity
|
||||
{
|
||||
public DictEntity(DictEntityUid uid)
|
||||
{
|
||||
Uid = uid;
|
||||
}
|
||||
|
||||
public DictEntityUid Uid { get; }
|
||||
|
||||
public bool Deleted { get; set; }
|
||||
}
|
||||
|
||||
private sealed class GenEntity
|
||||
{
|
||||
public GenEntityUid Uid { get; }
|
||||
|
||||
public bool Deleted { get; set; }
|
||||
|
||||
public GenEntity(GenEntityUid uid)
|
||||
{
|
||||
Uid = uid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user