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>
<PackageReference Include="MemoryPack" Version="1.21.4" />
<PackageReference Include="MessagePack" Version="3.1.4" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

View File

@ -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;
/// <summary>
/// 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<TestOrder>(_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<TestOrder>(_serialized);
public void Deserialize() => MemoryPackSerializer.Deserialize<TestOrder>(_serialized);
}
private sealed class MessagePackBenchmark : ISerializerBenchmark

View File

@ -11,6 +11,7 @@
<Import Project="..//AyCode.Core.targets" />
<ItemGroup>
<PackageReference Include="MemoryPack" Version="1.21.4" />
<PackageReference Include="MessagePack" Version="3.1.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" 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.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&lt;int&gt; for semantic $id/$ref serialization.
/// </summary>
[MemoryPackable]
[AcBinarySerializable]
[MessagePackObject]
public class SharedTag : IId<int>
public partial class SharedTag : IId<int>
{
[Key(0)]
public int Id { get; set; }
@ -77,9 +79,10 @@ public class SharedTag : IId<int>
/// <summary>
/// Shared category - for hierarchical cross-reference testing.
/// </summary>
[MemoryPackable]
[AcBinarySerializable]
[MessagePackObject]
public class SharedCategory : IId<int>
public partial class SharedCategory : IId<int>
{
[Key(0)]
public int Id { get; set; }
@ -102,9 +105,10 @@ public class SharedCategory : IId<int>
/// <summary>
/// Shared user reference - appears in many places to test $ref deduplication.
/// </summary>
[MemoryPackable]
[AcBinarySerializable]
[MessagePackObject]
public class SharedUser : IId<int>
public partial class SharedUser : IId<int>
{
[Key(0)]
public int Id { get; set; }
@ -131,9 +135,10 @@ public class SharedUser : IId<int>
/// <summary>
/// User preferences - non-IId nested object
/// </summary>
[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.
/// </summary>
[MemoryPackable]
[AcBinarySerializable]
[MessagePackObject]
public class MetadataInfo
public partial class MetadataInfo
{
[AcStringIntern(true)]
[Key(0)]
@ -183,9 +189,10 @@ public class MetadataInfo
/// <summary>
/// Level 1: Main order - root of the hierarchy
/// </summary>
[MemoryPackable]
[AcBinarySerializable]
[MessagePackObject]
public class TestOrder : IId<int>
public partial class TestOrder : IId<int>
{
[Key(0)]
public int Id { get; set; }
@ -232,6 +239,7 @@ public class TestOrder : IId<int>
public List<TestOrderItem> 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<int>
/// <summary>
/// Level 2: Order item with pallets
/// </summary>
[MemoryPackable]
[AcBinarySerializable]
[MessagePackObject]
public class TestOrderItem : IId<int>
public partial class TestOrderItem : IId<int>
{
[Key(0)]
public int Id { get; set; }
@ -270,6 +279,7 @@ public class TestOrderItem : IId<int>
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<int>
/// <summary>
/// Level 3: Pallet containing measurements
/// </summary>
[MemoryPackable]
[AcBinarySerializable]
[MessagePackObject]
public class TestPallet : IId<int>
public partial class TestPallet : IId<int>
{
[Key(0)]
public int Id { get; set; }
@ -311,6 +322,7 @@ public class TestPallet : IId<int>
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<int>
/// <summary>
/// Level 4: Measurement with multiple points
/// </summary>
[MemoryPackable]
[AcBinarySerializable]
[MessagePackObject]
public class TestMeasurement : IId<int>
public partial class TestMeasurement : IId<int>
{
[Key(0)]
public int Id { get; set; }
@ -344,6 +357,7 @@ public class TestMeasurement : IId<int>
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<int>
/// <summary>
/// Level 5: Deepest level - measurement point
/// </summary>
[MemoryPackable]
[AcBinarySerializable]
[MessagePackObject]
public class TestMeasurementPoint : IId<int>
public partial class TestMeasurementPoint : IId<int>
{
[Key(0)]
public int Id { get; set; }
@ -373,6 +388,7 @@ public class TestMeasurementPoint : IId<int>
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
/// <summary>
/// Class with all primitive types for WASM/serialization testing
/// </summary>
[MemoryPackable]
[AcBinarySerializable]
public class PrimitiveTestClass
public partial class PrimitiveTestClass
{
public int IntValue { get; set; }
public long LongValue { get; set; }

View File

@ -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);

View File

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