From 09c492c344d54bee7e3c8a2d51ae2241bbd97dc9 Mon Sep 17 00:00:00 2001 From: halgari Date: Wed, 10 Jan 2024 12:57:59 -0700 Subject: [PATCH] Benchmark work and performance tuning of serialization --- .../ABenchmark.cs | 17 ++++- .../AccumulatorBenchmarks.cs | 14 +++- .../EntityContextBenchmarks.cs | 9 +-- .../EventStoreBenchmarks.cs | 7 +- .../Program.cs | 16 ++++- .../ReadBenchmarks.cs | 9 +-- .../SerializationBenchmarks.cs | 69 +++++++++++++++++++ .../WriteBenchmarks.cs | 9 +-- .../PooledMemoryBufferWriter.cs | 36 ++++++---- .../Serialization/EventSerializer.cs | 4 ++ .../Model/Loadout.cs | 13 +++- 11 files changed, 165 insertions(+), 38 deletions(-) create mode 100644 benchmarks/NexusMods.EventSourcing.Benchmarks/SerializationBenchmarks.cs diff --git a/benchmarks/NexusMods.EventSourcing.Benchmarks/ABenchmark.cs b/benchmarks/NexusMods.EventSourcing.Benchmarks/ABenchmark.cs index afa9cf56..03dbc714 100644 --- a/benchmarks/NexusMods.EventSourcing.Benchmarks/ABenchmark.cs +++ b/benchmarks/NexusMods.EventSourcing.Benchmarks/ABenchmark.cs @@ -2,7 +2,10 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using NexusMods.EventSourcing.Abstractions; +using NexusMods.EventSourcing.RocksDB; +using NexusMods.EventSourcing.Serialization; using NexusMods.EventSourcing.TestModel; +using NexusMods.Paths; namespace NexusMods.EventSourcing.Benchmarks; @@ -27,11 +30,19 @@ public ABenchmark() public void MakeStore(Type type) { - var serializer = Services.GetRequiredService(); + var serializer = Services.GetRequiredService(); IEventStore eventStore; - if (type == typeof(InMemoryEventStore)) + if (type == typeof(InMemoryEventStore)) { - eventStore = new InMemoryEventStore(serializer); + eventStore = new InMemoryEventStore(serializer); + } + else if (type == typeof(RocksDBEventStore)) + { + eventStore = new RocksDBEventStore(serializer, + new RocksDB.Settings + { + StorageLocation = FileSystem.Shared.GetKnownPath(KnownPath.EntryDirectory).Combine("FasterKV.EventStore" + Guid.NewGuid()) + }); } else { diff --git a/benchmarks/NexusMods.EventSourcing.Benchmarks/AccumulatorBenchmarks.cs b/benchmarks/NexusMods.EventSourcing.Benchmarks/AccumulatorBenchmarks.cs index 518f9e2c..185080ee 100644 --- a/benchmarks/NexusMods.EventSourcing.Benchmarks/AccumulatorBenchmarks.cs +++ b/benchmarks/NexusMods.EventSourcing.Benchmarks/AccumulatorBenchmarks.cs @@ -3,6 +3,7 @@ using System.Linq; using BenchmarkDotNet.Attributes; using NexusMods.EventSourcing.Abstractions; +using NexusMods.EventSourcing.Serialization; using NexusMods.EventSourcing.TestModel; using NexusMods.EventSourcing.TestModel.Events; using NexusMods.EventSourcing.TestModel.Model; @@ -15,10 +16,11 @@ public class AccumulatorBenchmarks : ABenchmark private readonly EntityContext _ctx; private readonly LoadoutRegistry _registry; private readonly Loadout[] _loadouts; + private readonly int _numLoadouts; public AccumulatorBenchmarks() { - MakeStore(typeof(InMemoryEventStore)); + MakeStore(typeof(InMemoryEventStore)); _ctx = new EntityContext(EventStore); _ctx.Add(new CreateLoadout(EntityId.NewId(), "Test")); @@ -29,12 +31,18 @@ public AccumulatorBenchmarks() throw new Exception("Bad state"); _loadouts = _registry.Loadouts.ToArray(); + _numLoadouts = _loadouts.Length; } [Benchmark] - public string GetMultiAttributeItems() + public int GetMultiAttributeItems() { - return _loadouts[0].Name; + var size = 0; + for (var j = 0; j < 10_000_000; j++) + { + size += _loadouts[0].Name.Length; + } + return size; } } diff --git a/benchmarks/NexusMods.EventSourcing.Benchmarks/EntityContextBenchmarks.cs b/benchmarks/NexusMods.EventSourcing.Benchmarks/EntityContextBenchmarks.cs index cd4478e2..96a79b02 100644 --- a/benchmarks/NexusMods.EventSourcing.Benchmarks/EntityContextBenchmarks.cs +++ b/benchmarks/NexusMods.EventSourcing.Benchmarks/EntityContextBenchmarks.cs @@ -2,6 +2,7 @@ using BenchmarkDotNet.Attributes; using NexusMods.EventSourcing.Abstractions; using NexusMods.EventSourcing.RocksDB; +using NexusMods.EventSourcing.Serialization; using NexusMods.EventSourcing.TestModel; using NexusMods.EventSourcing.TestModel.Events; using NexusMods.EventSourcing.TestModel.Model; @@ -15,10 +16,10 @@ public class EntityContextBenchmarks : ABenchmark private EntityId[] _ids = Array.Empty>(); private EntityContext _context = null!; - [Params(typeof(InMemoryEventStore), - //typeof(FasterKVEventStore), - typeof(RocksDBEventStore))] - public Type EventStoreType { get; set; } = typeof(InMemoryEventStore); + [Params(typeof(InMemoryEventStore), + //typeof(FasterKVEventStore), + typeof(RocksDBEventStore))] + public Type EventStoreType { get; set; } = typeof(InMemoryEventStore); [Params(100, 1000)] public int EventCount { get; set; } diff --git a/benchmarks/NexusMods.EventSourcing.Benchmarks/EventStoreBenchmarks.cs b/benchmarks/NexusMods.EventSourcing.Benchmarks/EventStoreBenchmarks.cs index 7ae06fed..d65eb7b0 100644 --- a/benchmarks/NexusMods.EventSourcing.Benchmarks/EventStoreBenchmarks.cs +++ b/benchmarks/NexusMods.EventSourcing.Benchmarks/EventStoreBenchmarks.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.Logging; using NexusMods.EventSourcing.Abstractions; using NexusMods.EventSourcing.RocksDB; +using NexusMods.EventSourcing.Serialization; using NexusMods.EventSourcing.TestModel; using NexusMods.EventSourcing.TestModel.Events; using NexusMods.EventSourcing.TestModel.Model; @@ -29,9 +30,9 @@ public EventStoreBenchmarks() } - [Params(typeof(InMemoryEventStore), - //typeof(FasterKVEventStore), - typeof(RocksDBEventStore))] + [Params(typeof(InMemoryEventStore), + //typeof(FasterKVEventStore), + typeof(RocksDBEventStore))] public Type EventStoreType { get; set; } = null!; [GlobalSetup] diff --git a/benchmarks/NexusMods.EventSourcing.Benchmarks/Program.cs b/benchmarks/NexusMods.EventSourcing.Benchmarks/Program.cs index 4f1a3c20..1efe0b27 100644 --- a/benchmarks/NexusMods.EventSourcing.Benchmarks/Program.cs +++ b/benchmarks/NexusMods.EventSourcing.Benchmarks/Program.cs @@ -4,12 +4,14 @@ using BenchmarkDotNet.Running; using NexusMods.EventSourcing.Benchmarks; using NexusMods.EventSourcing.RocksDB; +using NexusMods.EventSourcing.Serialization; using NexusMods.EventSourcing.TestModel; + /* #if DEBUG var readBenchmarks = new EntityContextBenchmarks(); -readBenchmarks.EventStoreType = typeof(RocksDBEventStore); +readBenchmarks.EventStoreType = typeof(RocksDBEventStore); readBenchmarks.EventCount = 1000; readBenchmarks.EntityCount = 1000; Console.WriteLine("Setup"); @@ -18,10 +20,11 @@ readBenchmarks.LoadAllEntities(); Console.WriteLine("LoadAllEntities done"); #else -BenchmarkRunner.Run(); +BenchmarkRunner.Run(); #endif */ + #if DEBUG var benchmarks = new AccumulatorBenchmarks(); for (int i = 0; i < 10_000_000; i++) @@ -32,3 +35,12 @@ #else BenchmarkRunner.Run(); #endif + +/* +| Method | Mean | Error | StdDev | Gen0 | Allocated | +|------------ |---------:|--------:|--------:|-------:|----------:| +| Serialize | 174.2 ns | 0.42 ns | 0.37 ns | - | - | +| Deserialize | 133.7 ns | 0.80 ns | 0.75 ns | 0.0312 | 592 B | + + +*/ diff --git a/benchmarks/NexusMods.EventSourcing.Benchmarks/ReadBenchmarks.cs b/benchmarks/NexusMods.EventSourcing.Benchmarks/ReadBenchmarks.cs index fe87698a..4c540ed4 100644 --- a/benchmarks/NexusMods.EventSourcing.Benchmarks/ReadBenchmarks.cs +++ b/benchmarks/NexusMods.EventSourcing.Benchmarks/ReadBenchmarks.cs @@ -2,6 +2,7 @@ using BenchmarkDotNet.Attributes; using NexusMods.EventSourcing.Abstractions; using NexusMods.EventSourcing.RocksDB; +using NexusMods.EventSourcing.Serialization; using NexusMods.EventSourcing.TestModel; using NexusMods.EventSourcing.TestModel.Events; using NexusMods.EventSourcing.TestModel.Model; @@ -13,10 +14,10 @@ public class ReadBenchmarks : ABenchmark { private EntityId[] _ids = Array.Empty>(); - [Params(typeof(InMemoryEventStore), - //typeof(FasterKVEventStore), - typeof(RocksDBEventStore))] - public Type EventStoreType { get; set; } = typeof(InMemoryEventStore); + [Params(typeof(InMemoryEventStore), + //typeof(FasterKVEventStore), + typeof(RocksDBEventStore))] + public Type EventStoreType { get; set; } = typeof(InMemoryEventStore); [Params(100, 1000)] public int EventCount { get; set; } diff --git a/benchmarks/NexusMods.EventSourcing.Benchmarks/SerializationBenchmarks.cs b/benchmarks/NexusMods.EventSourcing.Benchmarks/SerializationBenchmarks.cs new file mode 100644 index 00000000..cbe5ac2f --- /dev/null +++ b/benchmarks/NexusMods.EventSourcing.Benchmarks/SerializationBenchmarks.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using BenchmarkDotNet.Attributes; +using Microsoft.Extensions.DependencyInjection; +using NexusMods.EventSourcing.Abstractions; +using NexusMods.EventSourcing.Serialization; +using NexusMods.EventSourcing.TestModel.Events; +using NexusMods.EventSourcing.TestModel.Model; + +namespace NexusMods.EventSourcing.Benchmarks; + +[MemoryDiagnoser] +public class SerializationBenchmarks : ABenchmark +{ + private readonly IEvent[] _events; + private BinaryEventSerializer _serializer = null!; + private byte[][] _serializedEvents = null!; + + public SerializationBenchmarks() + { + _events = + [ + new CreateLoadout(EntityId.NewId(), "Test"), + new RenameLoadout(EntityId.NewId(), "Test"), + new AddMod("New Mod", true, EntityId.NewId(), new EntityId()), + new AddCollection(EntityId.NewId(), "NewCollection", EntityId.NewId(), + [EntityId.NewId()]), + new DeleteMod(EntityId.NewId(), EntityId.NewId()), + new RenameLoadout(EntityId.NewId(), "Test"), + new SwapModEnabled(EntityId.NewId(), true) + ]; + } + + [GlobalSetup] + public void Setup() + { + _serializer = Services.GetRequiredService(); + + _serializedEvents = _events.Select(evnt => _serializer.Serialize(evnt).ToArray()).ToArray(); + } + + [Benchmark] + public int Serialize() + { + var size = 0; + for (var i = 0; i < _events.Length; i++) + { + var evnt = _events[i]; + size += _serializer.Serialize(evnt).Length; + } + + return size; + } + + [Benchmark] + public int Deserialize() + { + var size = 0; + for (var i = 0; i < _serializedEvents.Length; i++) + { + var evnt = _serializedEvents[i]; + _serializer.Deserialize(evnt); + size += 1; + } + return size; + } + +} diff --git a/benchmarks/NexusMods.EventSourcing.Benchmarks/WriteBenchmarks.cs b/benchmarks/NexusMods.EventSourcing.Benchmarks/WriteBenchmarks.cs index 81f28811..38a8bb71 100644 --- a/benchmarks/NexusMods.EventSourcing.Benchmarks/WriteBenchmarks.cs +++ b/benchmarks/NexusMods.EventSourcing.Benchmarks/WriteBenchmarks.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Hosting; using NexusMods.EventSourcing.Abstractions; using NexusMods.EventSourcing.RocksDB; +using NexusMods.EventSourcing.Serialization; using NexusMods.EventSourcing.TestModel; using NexusMods.EventSourcing.TestModel.Events; using NexusMods.EventSourcing.TestModel.Model; @@ -16,10 +17,10 @@ public class WriteBenchmarks : ABenchmark { private readonly IEvent[] _events; - [Params(typeof(InMemoryEventStore), - //typeof(FasterKVEventStore), - typeof(RocksDBEventStore))] - public Type EventStoreType { get; set; } = typeof(InMemoryEventStore); + [Params(typeof(InMemoryEventStore), + //typeof(FasterKVEventStore), + typeof(RocksDBEventStore))] + public Type EventStoreType { get; set; } = typeof(InMemoryEventStore); [Params(100, 1000, 10000)] public int EventCount { get; set; } = 100; diff --git a/src/NexusMods.EventSourcing/PooledMemoryBufferWriter.cs b/src/NexusMods.EventSourcing/PooledMemoryBufferWriter.cs index 55325501..cd1305ee 100644 --- a/src/NexusMods.EventSourcing/PooledMemoryBufferWriter.cs +++ b/src/NexusMods.EventSourcing/PooledMemoryBufferWriter.cs @@ -1,17 +1,23 @@ using System; using System.Buffers; +using System.Runtime.CompilerServices; +using Reloaded.Memory.Extensions; namespace NexusMods.EventSourcing; -public class PooledMemoryBufferWriter : IBufferWriter +public sealed class PooledMemoryBufferWriter : IBufferWriter { - private IMemoryOwner _data; + private IMemoryOwner _owner; + private Memory _data; private int _idx; + private int _size; public PooledMemoryBufferWriter(int initialCapacity = 1024) { - _data = MemoryPool.Shared.Rent(initialCapacity); + _owner = MemoryPool.Shared.Rent(initialCapacity); + _data = _owner.Memory; _idx = 0; + _size = initialCapacity; } public void Reset() @@ -19,36 +25,40 @@ public void Reset() _idx = 0; } - public ReadOnlySpan GetWrittenSpan() => _data.Memory[.._idx].Span; + public ReadOnlySpan GetWrittenSpan() => _data.Span.SliceFast(0, _idx); private void Expand() { - var newSize = _data.Memory.Length * 2; + var newSize = _data.Length * 2; var newData = MemoryPool.Shared.Rent(newSize); - _data.Memory.CopyTo(newData.Memory); - _data.Dispose(); - _data = newData; + _data.CopyTo(newData.Memory); + _owner.Dispose(); + _owner = newData; + _data = newData.Memory; + _size = newSize; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Advance(int count) { - if (_idx + count > _data.Memory.Length) + if (_idx + count > _size) Expand(); _idx += count; } public Memory GetMemory(int sizeHint = 0) { - if (_idx + sizeHint > _data.Memory.Length) + if (_idx + sizeHint > _size) Expand(); - return _data.Memory[_idx..]; + return _data[_idx..]; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span GetSpan(int sizeHint = 0) { - if (_idx + sizeHint > _data.Memory.Length) + if (_idx + sizeHint > _size) Expand(); - return _data.Memory.Span[_idx..]; + return _data.Span.SliceFast(_idx); } } diff --git a/src/NexusMods.EventSourcing/Serialization/EventSerializer.cs b/src/NexusMods.EventSourcing/Serialization/EventSerializer.cs index d28a1c2b..1bfdd9d3 100644 --- a/src/NexusMods.EventSourcing/Serialization/EventSerializer.cs +++ b/src/NexusMods.EventSourcing/Serialization/EventSerializer.cs @@ -308,12 +308,14 @@ private static void SortParams(MemberDefinition[] paramDefinitions, out bool isF isFixedSize = unfixedParams.Count == 0; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static Span SliceFastStart(ReadOnlySpan data, int start) { return MemoryMarshal.CreateSpan(ref Unsafe.Add(ref MemoryMarshal.GetReference(data), start), data.Length - start); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static Span SliceFastStartLength(Span data, int start, int length) { return MemoryMarshal.CreateSpan(ref Unsafe.Add(ref MemoryMarshal.GetReference(data), start), length); @@ -322,11 +324,13 @@ internal static Span SliceFastStartLength(Span data, int start, int private MethodInfo _sliceFastStartLengthMethodInfo = typeof(BinaryEventSerializer).GetMethod(nameof(SliceFastStartLength), BindingFlags.Static | BindingFlags.NonPublic)!; + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ReadOnlySpan ReadOnlySliceFastStartLength(ReadOnlySpan data, int start, int length) { return MemoryMarshal.CreateSpan(ref Unsafe.Add(ref MemoryMarshal.GetReference(data), start), length); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ReadOnlySpan ReadOnlySliceFastStart(ReadOnlySpan data, int start) { return MemoryMarshal.CreateSpan(ref Unsafe.Add(ref MemoryMarshal.GetReference(data), start), data.Length - start); diff --git a/tests/NexusMods.EventSourcing.TestModel/Model/Loadout.cs b/tests/NexusMods.EventSourcing.TestModel/Model/Loadout.cs index de199961..f997e416 100644 --- a/tests/NexusMods.EventSourcing.TestModel/Model/Loadout.cs +++ b/tests/NexusMods.EventSourcing.TestModel/Model/Loadout.cs @@ -1,4 +1,5 @@ using System.Collections.ObjectModel; +using System.Runtime.CompilerServices; using DynamicData; using NexusMods.EventSourcing.Abstractions; @@ -9,8 +10,16 @@ public class Loadout(IEntityContext context, EntityId id) : AEntity /// The human readable name of the loadout. /// - public string Name => _name.Get(this); - internal static readonly ScalarAttribute _name = new(nameof(Name)); + public string Name + { + get + { + CallSite> site; + return _name.Get(this); + } + } + + internal static readonly dynamic _name = new ScalarAttribute(nameof(Name)); /// /// The mods in the loadout.