From dcd44cf7057f13215621fd4528db9f15c61effcb Mon Sep 17 00:00:00 2001 From: Loretta Date: Mon, 16 Feb 2026 07:59:24 +0100 Subject: [PATCH] Add MemoryPack benchmarks and model support - Integrated MemoryPack as a serializer in the benchmark suite - Added [MemoryPackable] and [MemoryPackIgnore] to test models - Enabled AcBinary source generation by default - Updated benchmark app to include MemoryPack and focus on key serializers - Added MemoryPack NuGet references to projects - Refactored AcBinarySerializer.WriteString flag handling --- .../AyCode.Core.Serializers.Console.csproj | 1 + AyCode.Core.Serializers.Console/Program.cs | 44 +++++++++---------- AyCode.Core.Tests/AyCode.Core.Tests.csproj | 1 + .../TestModels/SharedTestModels.cs | 39 +++++++++++----- .../Binaries/AcBinarySerializer.cs | 9 ++-- .../Binaries/AcBinarySerializerOptions.cs | 2 +- 6 files changed, 57 insertions(+), 39 deletions(-) diff --git a/AyCode.Core.Serializers.Console/AyCode.Core.Serializers.Console.csproj b/AyCode.Core.Serializers.Console/AyCode.Core.Serializers.Console.csproj index 32fbf97..0f900d8 100644 --- a/AyCode.Core.Serializers.Console/AyCode.Core.Serializers.Console.csproj +++ b/AyCode.Core.Serializers.Console/AyCode.Core.Serializers.Console.csproj @@ -6,6 +6,7 @@ + diff --git a/AyCode.Core.Serializers.Console/Program.cs b/AyCode.Core.Serializers.Console/Program.cs index 77ef1df..9900348 100644 --- a/AyCode.Core.Serializers.Console/Program.cs +++ b/AyCode.Core.Serializers.Console/Program.cs @@ -1,7 +1,7 @@ using AyCode.Core.Compression; using AyCode.Core.Serializers.Binaries; -using AyCode.Core.Serializers.Jsons; using AyCode.Core.Tests.TestModels; +using MemoryPack; using MessagePack; using MessagePack.Resolvers; using Microsoft.Extensions.Options; @@ -15,7 +15,7 @@ namespace AyCode.Core.Serializers.Console; /// /// Comprehensive benchmark application for all serializers. -/// Compares: AcBinary (all options), AcJson, MessagePack, Newtonsoft.Json, System.Text.Json +/// Compares: AcBinary (all options), MemoryPack, MessagePack, Newtonsoft.Json, System.Text.Json /// /// Usage: /// dotnet run # Run all benchmarks @@ -39,7 +39,7 @@ public static class Program private const string SerializerAcBinaryNoRef = "AcBinary (NoRef)"; private const string SerializerAcBinaryFastMode = "AcBinary (FastMode)"; private const string SerializerAcBinaryNoIntern = "AcBinary (NoIntern)"; - private const string SerializerAcJsonDefault = "AcJson (Default)"; + private const string SerializerMemoryPack = "MemoryPack"; private const string SerializerAcBinaryBufferWriter = "AcBinary (BufferWriter)"; private const string SerializerSystemTextJson = "System.Text.Json"; @@ -212,18 +212,18 @@ public static class Program { // AcBinary variants - new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.Default, SerializerAcBinaryDefault), - new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.WithoutReferenceHandling, SerializerAcBinaryNoRef), - new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.FastMode, SerializerAcBinaryFastMode), - new AcBinaryBenchmark(testData.Order, new AcBinarySerializerOptions { UseStringInterning = StringInterningMode.None }, SerializerAcBinaryNoIntern), - - //new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.FastMode, SerializerAcBinaryDefault), - //new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.FastMode, SerializerAcBinaryNoRef), + //new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.Default, SerializerAcBinaryDefault), + //new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.WithoutReferenceHandling, SerializerAcBinaryNoRef), //new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.FastMode, SerializerAcBinaryFastMode), - //new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.FastMode, SerializerAcBinaryNoIntern), + //new AcBinaryBenchmark(testData.Order, new AcBinarySerializerOptions { UseStringInterning = StringInterningMode.None }, SerializerAcBinaryNoIntern), - // AcJson - new AcJsonBenchmark(testData.Order, AcJsonSerializerOptions.Default, SerializerAcJsonDefault), + new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.FastMode, SerializerAcBinaryDefault), + new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.FastMode, SerializerAcBinaryNoRef), + new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.FastMode, SerializerAcBinaryFastMode), + new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.FastMode, SerializerAcBinaryNoIntern), + + // MemoryPack + new MemoryPackBenchmark(testData.Order, SerializerMemoryPack), // MessagePack new MessagePackBenchmark(testData.Order, SerializerMessagePack), @@ -295,23 +295,19 @@ public static class Program public void Deserialize() => AcBinaryDeserializer.Deserialize(_serialized, _options); } - private sealed class AcJsonBenchmark : ISerializerBenchmark + private sealed class MemoryPackBenchmark : ISerializerBenchmark { private readonly TestOrder _order; - private readonly AcJsonSerializerOptions _options; - private readonly string _serialized; - private readonly byte[] _serializedUtf8; + private readonly byte[] _serialized; public string Name { get; } - public int SerializedSize => _serializedUtf8.Length; + public int SerializedSize => _serialized.Length; - public AcJsonBenchmark(TestOrder order, AcJsonSerializerOptions options, string name) + public MemoryPackBenchmark(TestOrder order, string name) { _order = order; - _options = options; Name = name; - _serialized = AcJsonSerializer.Serialize(order, options); - _serializedUtf8 = Utf8NoBom.GetBytes(_serialized); + _serialized = MemoryPackSerializer.Serialize(order); } public void Warmup(int iterations) @@ -324,10 +320,10 @@ public static class Program } [MethodImpl(MethodImplOptions.NoInlining)] - public void Serialize() => AcJsonSerializer.Serialize(_order, _options); + public void Serialize() => MemoryPackSerializer.Serialize(_order); [MethodImpl(MethodImplOptions.NoInlining)] - public void Deserialize() => AcJsonDeserializer.Deserialize(_serialized); + public void Deserialize() => MemoryPackSerializer.Deserialize(_serialized); } private sealed class MessagePackBenchmark : ISerializerBenchmark diff --git a/AyCode.Core.Tests/AyCode.Core.Tests.csproj b/AyCode.Core.Tests/AyCode.Core.Tests.csproj index 1bf6239..eceee77 100644 --- a/AyCode.Core.Tests/AyCode.Core.Tests.csproj +++ b/AyCode.Core.Tests/AyCode.Core.Tests.csproj @@ -11,6 +11,7 @@ + diff --git a/AyCode.Core.Tests/TestModels/SharedTestModels.cs b/AyCode.Core.Tests/TestModels/SharedTestModels.cs index 2ab95a8..906c765 100644 --- a/AyCode.Core.Tests/TestModels/SharedTestModels.cs +++ b/AyCode.Core.Tests/TestModels/SharedTestModels.cs @@ -3,6 +3,7 @@ using AyCode.Core.Interfaces; using AyCode.Core.Serializers.Attributes; using AyCode.Core.Serializers.Binaries; using AyCode.Core.Serializers.Jsons; +using MemoryPack; using MessagePack; using MongoDB.Bson.Serialization.Attributes; using Newtonsoft.Json; @@ -53,9 +54,10 @@ public enum TestUserRole /// Shared tag/label - used across multiple entities for cross-reference testing. /// Implements IId<int> for semantic $id/$ref serialization. /// +[MemoryPackable] [AcBinarySerializable] [MessagePackObject] -public class SharedTag : IId +public partial class SharedTag : IId { [Key(0)] public int Id { get; set; } @@ -77,9 +79,10 @@ public class SharedTag : IId /// /// Shared category - for hierarchical cross-reference testing. /// +[MemoryPackable] [AcBinarySerializable] [MessagePackObject] -public class SharedCategory : IId +public partial class SharedCategory : IId { [Key(0)] public int Id { get; set; } @@ -102,9 +105,10 @@ public class SharedCategory : IId /// /// Shared user reference - appears in many places to test $ref deduplication. /// +[MemoryPackable] [AcBinarySerializable] [MessagePackObject] -public class SharedUser : IId +public partial class SharedUser : IId { [Key(0)] public int Id { get; set; } @@ -131,9 +135,10 @@ public class SharedUser : IId /// /// User preferences - non-IId nested object /// +[MemoryPackable] [AcBinarySerializable] [MessagePackObject] -public class UserPreferences +public partial class UserPreferences { [AcStringIntern(true)] [Key(0)] @@ -156,9 +161,10 @@ public class UserPreferences /// Non-IId metadata class - uses Newtonsoft PreserveReferencesHandling (numeric $id/$ref). /// Does NOT implement IId, so uses standard Newtonsoft reference tracking. /// +[MemoryPackable] [AcBinarySerializable] [MessagePackObject] -public class MetadataInfo +public partial class MetadataInfo { [AcStringIntern(true)] [Key(0)] @@ -183,9 +189,10 @@ public class MetadataInfo /// /// Level 1: Main order - root of the hierarchy /// +[MemoryPackable] [AcBinarySerializable] [MessagePackObject] -public class TestOrder : IId +public partial class TestOrder : IId { [Key(0)] public int Id { get; set; } @@ -232,6 +239,7 @@ public class TestOrder : IId public List NoMergeItems { get; set; } = []; // Parent reference - ignored by all serializers to prevent circular references + [MemoryPackIgnore] [JsonIgnore] [IgnoreMember] [BsonIgnore] @@ -241,9 +249,10 @@ public class TestOrder : IId /// /// Level 2: Order item with pallets /// +[MemoryPackable] [AcBinarySerializable] [MessagePackObject] -public class TestOrderItem : IId +public partial class TestOrderItem : IId { [Key(0)] public int Id { get; set; } @@ -270,6 +279,7 @@ public class TestOrderItem : IId public MetadataInfo? ItemMetadata { get; set; } // Parent reference - ignored by all serializers to prevent circular references + [MemoryPackIgnore] [JsonIgnore] [IgnoreMember] [BsonIgnore] @@ -279,9 +289,10 @@ public class TestOrderItem : IId /// /// Level 3: Pallet containing measurements /// +[MemoryPackable] [AcBinarySerializable] [MessagePackObject] -public class TestPallet : IId +public partial class TestPallet : IId { [Key(0)] public int Id { get; set; } @@ -311,6 +322,7 @@ public class TestPallet : IId public MetadataInfo? PalletMetadata { get; set; } // Parent reference - ignored by all serializers to prevent circular references + [MemoryPackIgnore] [JsonIgnore] [IgnoreMember] [BsonIgnore] @@ -320,9 +332,10 @@ public class TestPallet : IId /// /// Level 4: Measurement with multiple points /// +[MemoryPackable] [AcBinarySerializable] [MessagePackObject] -public class TestMeasurement : IId +public partial class TestMeasurement : IId { [Key(0)] public int Id { get; set; } @@ -344,6 +357,7 @@ public class TestMeasurement : IId public SharedUser? Operator { get; set; } // Parent reference - ignored by all serializers to prevent circular references + [MemoryPackIgnore] [JsonIgnore] [IgnoreMember] [BsonIgnore] @@ -353,9 +367,10 @@ public class TestMeasurement : IId /// /// Level 5: Deepest level - measurement point /// +[MemoryPackable] [AcBinarySerializable] [MessagePackObject] -public class TestMeasurementPoint : IId +public partial class TestMeasurementPoint : IId { [Key(0)] public int Id { get; set; } @@ -373,6 +388,7 @@ public class TestMeasurementPoint : IId public SharedUser? Verifier { get; set; } // Parent reference - ignored by all serializers to prevent circular references + [MemoryPackIgnore] [JsonIgnore] [IgnoreMember] [BsonIgnore] @@ -449,8 +465,9 @@ public class TestOrderWithNullableCollections /// /// Class with all primitive types for WASM/serialization testing /// +[MemoryPackable] [AcBinarySerializable] -public class PrimitiveTestClass +public partial class PrimitiveTestClass { public int IntValue { get; set; } public long LongValue { get; set; } diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs index 5e02695..8e3fb30 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs @@ -912,10 +912,13 @@ public static partial class AcBinarySerializer return; } - if (context.StringInternEligible && context.IsValidForInterningString(value.Length)) - { - context.StringInternEligible = false; + // Read and immediately reset — prevents flag from leaking to subsequent WriteString calls + // (e.g. from TryWritePrimitive, WriteDictionary, or when IsValidForInterningString is false) + var internEligible = context.StringInternEligible; + context.StringInternEligible = false; + if (internEligible && context.IsValidForInterningString(value.Length)) + { if (context.TryConsumeWritePlanEntry(out var planEntry)) { ValidateWritePlanString(in planEntry, value); diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs index bbc3c38..a41a6df 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs @@ -84,7 +84,7 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions /// public bool UseMetadata { get; set; } = false; - public bool UseGeneratedCode { get; set; } = false; + public bool UseGeneratedCode { get; set; } = true; /// /// When true, checks for duplicate property name hashes during serialization (UseMetadata mode).