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
This commit is contained in:
Loretta 2026-02-16 07:59:24 +01:00
parent e30efff56c
commit dcd44cf705
6 changed files with 57 additions and 39 deletions

View File

@ -6,6 +6,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="MemoryPack" Version="1.21.4" />
<PackageReference Include="MessagePack" Version="3.1.4" /> <PackageReference Include="MessagePack" Version="3.1.4" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup> </ItemGroup>

View File

@ -1,7 +1,7 @@
using AyCode.Core.Compression; using AyCode.Core.Compression;
using AyCode.Core.Serializers.Binaries; using AyCode.Core.Serializers.Binaries;
using AyCode.Core.Serializers.Jsons;
using AyCode.Core.Tests.TestModels; using AyCode.Core.Tests.TestModels;
using MemoryPack;
using MessagePack; using MessagePack;
using MessagePack.Resolvers; using MessagePack.Resolvers;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -15,7 +15,7 @@ namespace AyCode.Core.Serializers.Console;
/// <summary> /// <summary>
/// Comprehensive benchmark application for all serializers. /// 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: /// Usage:
/// dotnet run # Run all benchmarks /// dotnet run # Run all benchmarks
@ -39,7 +39,7 @@ public static class Program
private const string SerializerAcBinaryNoRef = "AcBinary (NoRef)"; private const string SerializerAcBinaryNoRef = "AcBinary (NoRef)";
private const string SerializerAcBinaryFastMode = "AcBinary (FastMode)"; private const string SerializerAcBinaryFastMode = "AcBinary (FastMode)";
private const string SerializerAcBinaryNoIntern = "AcBinary (NoIntern)"; 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 SerializerAcBinaryBufferWriter = "AcBinary (BufferWriter)";
private const string SerializerSystemTextJson = "System.Text.Json"; private const string SerializerSystemTextJson = "System.Text.Json";
@ -212,18 +212,18 @@ public static class Program
{ {
// AcBinary variants // AcBinary variants
new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.Default, SerializerAcBinaryDefault), //new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.Default, SerializerAcBinaryDefault),
new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.WithoutReferenceHandling, SerializerAcBinaryNoRef), //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.FastMode, SerializerAcBinaryFastMode), //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 AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.FastMode, SerializerAcBinaryDefault),
new AcJsonBenchmark(testData.Order, AcJsonSerializerOptions.Default, SerializerAcJsonDefault), 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 // MessagePack
new MessagePackBenchmark(testData.Order, SerializerMessagePack), new MessagePackBenchmark(testData.Order, SerializerMessagePack),
@ -295,23 +295,19 @@ public static class Program
public void Deserialize() => AcBinaryDeserializer.Deserialize<TestOrder>(_serialized, _options); public void Deserialize() => AcBinaryDeserializer.Deserialize<TestOrder>(_serialized, _options);
} }
private sealed class AcJsonBenchmark : ISerializerBenchmark private sealed class MemoryPackBenchmark : ISerializerBenchmark
{ {
private readonly TestOrder _order; private readonly TestOrder _order;
private readonly AcJsonSerializerOptions _options; private readonly byte[] _serialized;
private readonly string _serialized;
private readonly byte[] _serializedUtf8;
public string Name { get; } 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; _order = order;
_options = options;
Name = name; Name = name;
_serialized = AcJsonSerializer.Serialize(order, options); _serialized = MemoryPackSerializer.Serialize(order);
_serializedUtf8 = Utf8NoBom.GetBytes(_serialized);
} }
public void Warmup(int iterations) public void Warmup(int iterations)
@ -324,10 +320,10 @@ public static class Program
} }
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public void Serialize() => AcJsonSerializer.Serialize(_order, _options); public void Serialize() => MemoryPackSerializer.Serialize(_order);
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public void Deserialize() => AcJsonDeserializer.Deserialize<TestOrder>(_serialized); public void Deserialize() => MemoryPackSerializer.Deserialize<TestOrder>(_serialized);
} }
private sealed class MessagePackBenchmark : ISerializerBenchmark private sealed class MessagePackBenchmark : ISerializerBenchmark

View File

@ -11,6 +11,7 @@
<Import Project="..//AyCode.Core.targets" /> <Import Project="..//AyCode.Core.targets" />
<ItemGroup> <ItemGroup>
<PackageReference Include="MemoryPack" Version="1.21.4" />
<PackageReference Include="MessagePack" Version="3.1.4" /> <PackageReference Include="MessagePack" Version="3.1.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.11" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.11" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.11" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.11" />

View File

@ -3,6 +3,7 @@ using AyCode.Core.Interfaces;
using AyCode.Core.Serializers.Attributes; using AyCode.Core.Serializers.Attributes;
using AyCode.Core.Serializers.Binaries; using AyCode.Core.Serializers.Binaries;
using AyCode.Core.Serializers.Jsons; using AyCode.Core.Serializers.Jsons;
using MemoryPack;
using MessagePack; using MessagePack;
using MongoDB.Bson.Serialization.Attributes; using MongoDB.Bson.Serialization.Attributes;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -53,9 +54,10 @@ public enum TestUserRole
/// Shared tag/label - used across multiple entities for cross-reference testing. /// Shared tag/label - used across multiple entities for cross-reference testing.
/// Implements IId&lt;int&gt; for semantic $id/$ref serialization. /// Implements IId&lt;int&gt; for semantic $id/$ref serialization.
/// </summary> /// </summary>
[MemoryPackable]
[AcBinarySerializable] [AcBinarySerializable]
[MessagePackObject] [MessagePackObject]
public class SharedTag : IId<int> public partial class SharedTag : IId<int>
{ {
[Key(0)] [Key(0)]
public int Id { get; set; } public int Id { get; set; }
@ -77,9 +79,10 @@ public class SharedTag : IId<int>
/// <summary> /// <summary>
/// Shared category - for hierarchical cross-reference testing. /// Shared category - for hierarchical cross-reference testing.
/// </summary> /// </summary>
[MemoryPackable]
[AcBinarySerializable] [AcBinarySerializable]
[MessagePackObject] [MessagePackObject]
public class SharedCategory : IId<int> public partial class SharedCategory : IId<int>
{ {
[Key(0)] [Key(0)]
public int Id { get; set; } public int Id { get; set; }
@ -102,9 +105,10 @@ public class SharedCategory : IId<int>
/// <summary> /// <summary>
/// Shared user reference - appears in many places to test $ref deduplication. /// Shared user reference - appears in many places to test $ref deduplication.
/// </summary> /// </summary>
[MemoryPackable]
[AcBinarySerializable] [AcBinarySerializable]
[MessagePackObject] [MessagePackObject]
public class SharedUser : IId<int> public partial class SharedUser : IId<int>
{ {
[Key(0)] [Key(0)]
public int Id { get; set; } public int Id { get; set; }
@ -131,9 +135,10 @@ public class SharedUser : IId<int>
/// <summary> /// <summary>
/// User preferences - non-IId nested object /// User preferences - non-IId nested object
/// </summary> /// </summary>
[MemoryPackable]
[AcBinarySerializable] [AcBinarySerializable]
[MessagePackObject] [MessagePackObject]
public class UserPreferences public partial class UserPreferences
{ {
[AcStringIntern(true)] [AcStringIntern(true)]
[Key(0)] [Key(0)]
@ -156,9 +161,10 @@ public class UserPreferences
/// Non-IId metadata class - uses Newtonsoft PreserveReferencesHandling (numeric $id/$ref). /// Non-IId metadata class - uses Newtonsoft PreserveReferencesHandling (numeric $id/$ref).
/// Does NOT implement IId, so uses standard Newtonsoft reference tracking. /// Does NOT implement IId, so uses standard Newtonsoft reference tracking.
/// </summary> /// </summary>
[MemoryPackable]
[AcBinarySerializable] [AcBinarySerializable]
[MessagePackObject] [MessagePackObject]
public class MetadataInfo public partial class MetadataInfo
{ {
[AcStringIntern(true)] [AcStringIntern(true)]
[Key(0)] [Key(0)]
@ -183,9 +189,10 @@ public class MetadataInfo
/// <summary> /// <summary>
/// Level 1: Main order - root of the hierarchy /// Level 1: Main order - root of the hierarchy
/// </summary> /// </summary>
[MemoryPackable]
[AcBinarySerializable] [AcBinarySerializable]
[MessagePackObject] [MessagePackObject]
public class TestOrder : IId<int> public partial class TestOrder : IId<int>
{ {
[Key(0)] [Key(0)]
public int Id { get; set; } public int Id { get; set; }
@ -232,6 +239,7 @@ public class TestOrder : IId<int>
public List<TestOrderItem> NoMergeItems { get; set; } = []; public List<TestOrderItem> NoMergeItems { get; set; } = [];
// Parent reference - ignored by all serializers to prevent circular references // Parent reference - ignored by all serializers to prevent circular references
[MemoryPackIgnore]
[JsonIgnore] [JsonIgnore]
[IgnoreMember] [IgnoreMember]
[BsonIgnore] [BsonIgnore]
@ -241,9 +249,10 @@ public class TestOrder : IId<int>
/// <summary> /// <summary>
/// Level 2: Order item with pallets /// Level 2: Order item with pallets
/// </summary> /// </summary>
[MemoryPackable]
[AcBinarySerializable] [AcBinarySerializable]
[MessagePackObject] [MessagePackObject]
public class TestOrderItem : IId<int> public partial class TestOrderItem : IId<int>
{ {
[Key(0)] [Key(0)]
public int Id { get; set; } public int Id { get; set; }
@ -270,6 +279,7 @@ public class TestOrderItem : IId<int>
public MetadataInfo? ItemMetadata { get; set; } public MetadataInfo? ItemMetadata { get; set; }
// Parent reference - ignored by all serializers to prevent circular references // Parent reference - ignored by all serializers to prevent circular references
[MemoryPackIgnore]
[JsonIgnore] [JsonIgnore]
[IgnoreMember] [IgnoreMember]
[BsonIgnore] [BsonIgnore]
@ -279,9 +289,10 @@ public class TestOrderItem : IId<int>
/// <summary> /// <summary>
/// Level 3: Pallet containing measurements /// Level 3: Pallet containing measurements
/// </summary> /// </summary>
[MemoryPackable]
[AcBinarySerializable] [AcBinarySerializable]
[MessagePackObject] [MessagePackObject]
public class TestPallet : IId<int> public partial class TestPallet : IId<int>
{ {
[Key(0)] [Key(0)]
public int Id { get; set; } public int Id { get; set; }
@ -311,6 +322,7 @@ public class TestPallet : IId<int>
public MetadataInfo? PalletMetadata { get; set; } public MetadataInfo? PalletMetadata { get; set; }
// Parent reference - ignored by all serializers to prevent circular references // Parent reference - ignored by all serializers to prevent circular references
[MemoryPackIgnore]
[JsonIgnore] [JsonIgnore]
[IgnoreMember] [IgnoreMember]
[BsonIgnore] [BsonIgnore]
@ -320,9 +332,10 @@ public class TestPallet : IId<int>
/// <summary> /// <summary>
/// Level 4: Measurement with multiple points /// Level 4: Measurement with multiple points
/// </summary> /// </summary>
[MemoryPackable]
[AcBinarySerializable] [AcBinarySerializable]
[MessagePackObject] [MessagePackObject]
public class TestMeasurement : IId<int> public partial class TestMeasurement : IId<int>
{ {
[Key(0)] [Key(0)]
public int Id { get; set; } public int Id { get; set; }
@ -344,6 +357,7 @@ public class TestMeasurement : IId<int>
public SharedUser? Operator { get; set; } public SharedUser? Operator { get; set; }
// Parent reference - ignored by all serializers to prevent circular references // Parent reference - ignored by all serializers to prevent circular references
[MemoryPackIgnore]
[JsonIgnore] [JsonIgnore]
[IgnoreMember] [IgnoreMember]
[BsonIgnore] [BsonIgnore]
@ -353,9 +367,10 @@ public class TestMeasurement : IId<int>
/// <summary> /// <summary>
/// Level 5: Deepest level - measurement point /// Level 5: Deepest level - measurement point
/// </summary> /// </summary>
[MemoryPackable]
[AcBinarySerializable] [AcBinarySerializable]
[MessagePackObject] [MessagePackObject]
public class TestMeasurementPoint : IId<int> public partial class TestMeasurementPoint : IId<int>
{ {
[Key(0)] [Key(0)]
public int Id { get; set; } public int Id { get; set; }
@ -373,6 +388,7 @@ public class TestMeasurementPoint : IId<int>
public SharedUser? Verifier { get; set; } public SharedUser? Verifier { get; set; }
// Parent reference - ignored by all serializers to prevent circular references // Parent reference - ignored by all serializers to prevent circular references
[MemoryPackIgnore]
[JsonIgnore] [JsonIgnore]
[IgnoreMember] [IgnoreMember]
[BsonIgnore] [BsonIgnore]
@ -449,8 +465,9 @@ public class TestOrderWithNullableCollections
/// <summary> /// <summary>
/// Class with all primitive types for WASM/serialization testing /// Class with all primitive types for WASM/serialization testing
/// </summary> /// </summary>
[MemoryPackable]
[AcBinarySerializable] [AcBinarySerializable]
public class PrimitiveTestClass public partial class PrimitiveTestClass
{ {
public int IntValue { get; set; } public int IntValue { get; set; }
public long LongValue { get; set; } public long LongValue { get; set; }

View File

@ -912,10 +912,13 @@ public static partial class AcBinarySerializer
return; return;
} }
if (context.StringInternEligible && context.IsValidForInterningString(value.Length)) // 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; context.StringInternEligible = false;
if (internEligible && context.IsValidForInterningString(value.Length))
{
if (context.TryConsumeWritePlanEntry(out var planEntry)) if (context.TryConsumeWritePlanEntry(out var planEntry))
{ {
ValidateWritePlanString(in planEntry, value); ValidateWritePlanString(in planEntry, value);

View File

@ -84,7 +84,7 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions
/// </summary> /// </summary>
public bool UseMetadata { get; set; } = false; public bool UseMetadata { get; set; } = false;
public bool UseGeneratedCode { get; set; } = false; public bool UseGeneratedCode { get; set; } = true;
/// <summary> /// <summary>
/// When true, checks for duplicate property name hashes during serialization (UseMetadata mode). /// When true, checks for duplicate property name hashes during serialization (UseMetadata mode).