321 lines
9.0 KiB
C#
321 lines
9.0 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|