From 406587c546ab4c68194872a453409e023c41a6e9 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Tue, 3 Sep 2024 20:25:24 +1000 Subject: [PATCH 1/4] Speedup TryGet Code done by ElectroJr not me, I just PRd it. --- src/Arch.Benchmarks/Benchmark.cs | 22 +------- src/Arch.Benchmarks/TryGetBenchmark.cs | 70 ++++++++++++++++++++++++++ src/Arch/Core/Archetype.cs | 22 ++++++++ src/Arch/Core/World.cs | 23 ++++----- 4 files changed, 104 insertions(+), 33 deletions(-) create mode 100644 src/Arch.Benchmarks/TryGetBenchmark.cs diff --git a/src/Arch.Benchmarks/Benchmark.cs b/src/Arch.Benchmarks/Benchmark.cs index eec4e08..5119809 100644 --- a/src/Arch.Benchmarks/Benchmark.cs +++ b/src/Arch.Benchmarks/Benchmark.cs @@ -9,7 +9,6 @@ public class Benchmark { private static void Main(string[] args) { - /* // NOTE: Can this be replaced with ManualConfig.CreateEmpty()? #pragma warning disable HAA0101 // Array allocation for params parameter var config = new ManualConfig() @@ -18,28 +17,9 @@ private static void Main(string[] args) .AddLogger(ConsoleLogger.Default) .AddColumnProvider(DefaultColumnProviders.Instance); #pragma warning restore HAA0101 // Array allocation for params parameter - */ - - - - var world = World.Create(); - for (var index = 0; index <= 100; index++) - { - world.Create(); - } - - var desc = new QueryDescription().WithAll(); - for (var index = 0; index <= 100000; index++) - { - world.Query(in desc, (ref int i) => - { - }); - } - - // NOTE: Is `-- --job` a typo? // Use: dotnet run -c Release --framework net7.0 -- --job short --filter *IterationBenchmark* - //BenchmarkSwitcher.FromAssembly(typeof(Benchmark).Assembly).Run(args, config); + BenchmarkSwitcher.FromAssembly(typeof(Benchmark).Assembly).Run(args, config); } } diff --git a/src/Arch.Benchmarks/TryGetBenchmark.cs b/src/Arch.Benchmarks/TryGetBenchmark.cs new file mode 100644 index 0000000..0361fbe --- /dev/null +++ b/src/Arch.Benchmarks/TryGetBenchmark.cs @@ -0,0 +1,70 @@ +using Arch.Core; +using Arch.Core.Utils; + +namespace Arch.Benchmarks; + +[HtmlExporter] +[MemoryDiagnoser] +// [HardwareCounters(HardwareCounter.CacheMisses)] +public class TryGetBenchmark +{ + private static World _world; + private static List _entities; + + private Consumer _consumer = new(); + + [GlobalSetup] + public void Setup() + { + _world = World.Create(); + + _entities = new List(1_000_000); + for (var index = 0; index < 1_000_000; index++) + { + _entities.Add(_world.Create(new Transform(), new Velocity())); + } + } + + [Benchmark] + public void TryGetGenericRef() + { + for (var index = 0; index < _entities.Count; index++) + { + var entity = _entities[index]; + var xform = _world.TryGetRef(entity, out var exists); + + if (exists) + { + _consumer.Consume(xform); + } + } + } + + [Benchmark] + public void TryGetGeneric() + { + for (var index = 0; index < _entities.Count; index++) + { + var entity = _entities[index]; + + if (_world.TryGet(entity, out var xform)) + { + _consumer.Consume(xform); + } + } + } + + [Benchmark] + public void TryGet() + { + for (var index = 0; index < _entities.Count; index++) + { + var entity = _entities[index]; + + if (_world.TryGet(entity, Component.GetComponentType(typeof(Transform)), out var xform)) + { + _consumer.Consume(xform); + } + } + } +} diff --git a/src/Arch/Core/Archetype.cs b/src/Arch/Core/Archetype.cs index c6a0519..12fe600 100644 --- a/src/Arch/Core/Archetype.cs +++ b/src/Arch/Core/Archetype.cs @@ -1,4 +1,5 @@ using System.Buffers; +using System.Diagnostics.Contracts; using Arch.Core.Extensions; using Arch.Core.Extensions.Internal; using Arch.Core.Utils; @@ -299,6 +300,27 @@ internal Archetype(Signature signature) _removeEdges = new SparseJaggedArray(BucketSize); } + /// + /// Try get the index of a component within this archetype. Returns false if the archetype does not have this + /// component. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + internal bool TryIndex(out int i) + { + var id = Component.ComponentType.Id; + Debug.Assert(id != -1, $"Index is out of bounds, component {typeof(T)} with id {id} does not exist in this chunk."); + + if (id >= _componentIdToArrayIndex.Length) + { + i = -1; + return false; + } + + i = _componentIdToArrayIndex.DangerousGetReferenceAt(id); + return i != -1; + } + /// /// The component types that the 's stored here have. /// diff --git a/src/Arch/Core/World.cs b/src/Arch/Core/World.cs index a200b4a..2e1b345 100644 --- a/src/Arch/Core/World.cs +++ b/src/Arch/Core/World.cs @@ -4,6 +4,7 @@ using Arch.Core.Extensions.Internal; using Arch.Core.Utils; using Collections.Pooled; +using CommunityToolkit.HighPerformance; using Schedulers; using Component = Arch.Core.Utils.Component; @@ -983,18 +984,18 @@ public ref T Get(Entity entity) [Pure] public bool TryGet(Entity entity, out T? component) { - component = default; - - var entitySlot = EntityInfo.GetEntitySlot(entity.Id); - var slot = entitySlot.Slot; - var archetype = entitySlot.Archetype; + ref var slot = ref EntityInfo.EntitySlots[entity.Id]; - if (!archetype.Has()) + if (!slot.Archetype.TryIndex(out int compIndex)) { + component = default; return false; } - component = archetype.Get(ref slot); + ref var chunk = ref slot.Archetype.Chunks[slot.Slot.ChunkIndex]; + Debug.Assert(compIndex != -1 && compIndex < chunk.Components.Length, $"Index is out of bounds, component {typeof(T)} with id {compIndex} does not exist in this chunk."); + var array = Unsafe.As(chunk.Components.DangerousGetReferenceAt(compIndex)); + component = array[slot.Slot.Index]; return true; } @@ -1009,16 +1010,14 @@ public bool TryGet(Entity entity, out T? component) [Pure] public ref T TryGetRef(Entity entity, out bool exists) { - var entitySlot = EntityInfo.GetEntitySlot(entity.Id); - var slot = entitySlot.Slot; - var archetype = entitySlot.Archetype; + ref var slot = ref EntityInfo.EntitySlots[entity.Id]; - if (!(exists = archetype.Has())) + if (!(exists = slot.Archetype.Has())) { return ref Unsafe.NullRef(); } - return ref archetype.Get(ref slot); + return ref slot.Archetype.Get(ref slot.Slot); } /// From 738ec7e3c391e959fb6047097e55760fbac0da63 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Tue, 3 Sep 2024 20:38:23 +1000 Subject: [PATCH 2/4] Tests --- src/Arch.Tests/WorldTest.cs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/Arch.Tests/WorldTest.cs b/src/Arch.Tests/WorldTest.cs index d219795..e49f6a9 100644 --- a/src/Arch.Tests/WorldTest.cs +++ b/src/Arch.Tests/WorldTest.cs @@ -604,6 +604,18 @@ public void Add() That(_world.GetArchetype(entity2), Is.EqualTo(_world.GetArchetype(entity))); That(arch, Is.EqualTo(_world.GetArchetype(entity))); } + + /// + /// Checks if generic TryGet works on entities. + /// + [Test] + public void TryGet() + { + var entity = _world.Create(new Transform()); + + That(_world.TryGet(entity, out Transform xform), Is.EqualTo(true)); + That(_world.TryGet(entity, out Rotation rot), Is.EqualTo(false)); + } } @@ -658,6 +670,18 @@ public void Add_NonGeneric() That(_world.GetArchetype(entity2), Is.EqualTo(_world.GetArchetype(entity))); That(arch, Is.EqualTo(_world.GetArchetype(entity))); } + + /// + /// Checks if generic TryGet works on entities. + /// + [Test] + public void TryGet_NonGeneric() + { + var entity = _world.Create(new Transform()); + + That(_world.TryGet(entity, Component.ComponentType, out var xform), Is.EqualTo(true)); + That(_world.TryGet(entity, Component.ComponentType, out var rot), Is.EqualTo(false)); + } } /// From e3d29206f3326e74c27f10d179cb4ea80954d5e7 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Tue, 3 Sep 2024 20:40:13 +1000 Subject: [PATCH 3/4] fix --- src/Arch.Benchmarks/TryGetBenchmark.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Arch.Benchmarks/TryGetBenchmark.cs b/src/Arch.Benchmarks/TryGetBenchmark.cs index 0361fbe..a88a71a 100644 --- a/src/Arch.Benchmarks/TryGetBenchmark.cs +++ b/src/Arch.Benchmarks/TryGetBenchmark.cs @@ -5,7 +5,7 @@ namespace Arch.Benchmarks; [HtmlExporter] [MemoryDiagnoser] -// [HardwareCounters(HardwareCounter.CacheMisses)] +[HardwareCounters(HardwareCounter.CacheMisses)] public class TryGetBenchmark { private static World _world; From 20a8491ade05bb959b4a58021f904795594b3770 Mon Sep 17 00:00:00 2001 From: metalgearsloth Date: Tue, 3 Sep 2024 20:48:47 +1000 Subject: [PATCH 4/4] Another minor increase --- src/Arch/Core/World.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Arch/Core/World.cs b/src/Arch/Core/World.cs index 2e1b345..d0f84a3 100644 --- a/src/Arch/Core/World.cs +++ b/src/Arch/Core/World.cs @@ -992,7 +992,7 @@ public bool TryGet(Entity entity, out T? component) return false; } - ref var chunk = ref slot.Archetype.Chunks[slot.Slot.ChunkIndex]; + ref var chunk = ref slot.Archetype.GetChunk(slot.Slot.ChunkIndex); Debug.Assert(compIndex != -1 && compIndex < chunk.Components.Length, $"Index is out of bounds, component {typeof(T)} with id {compIndex} does not exist in this chunk."); var array = Unsafe.As(chunk.Components.DangerousGetReferenceAt(compIndex)); component = array[slot.Slot.Index];