Add AcBinarySerializer tests, helpers, and benchmark updates
- Introduce AcBinarySerializerTests with full coverage for primitives, objects, collections, merge/populate, and size comparisons - Add AcBinarySerializer class stub as a placeholder for implementation - Extend serialization extension methods with binary helpers (ToBinary, BinaryTo, BinaryCloneTo, etc.) - Update test models to ignore parent references for all major serializers ([IgnoreMember], [BsonIgnore]) - Refactor benchmarks: split into minimal, simple, complex, and MessagePack comparison; add command-line switches and improved size reporting - Optimize AcJsonDeserializer with fast UTF-8 property lookup and direct primitive setting - Add MessagePack and MongoDB.Bson dependencies to test and benchmark projects - Add (accidentally) a summary of less commands as a documentation artifact
This commit is contained in:
parent
a945db9b09
commit
b9e83e2ef8
|
|
@ -8,9 +8,11 @@
|
|||
<Import Project="..//AyCode.Core.targets" />
|
||||
|
||||
<ItemGroup>
|
||||
<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" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||
<PackageReference Include="MongoDB.Bson" Version="3.5.2" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="4.0.2" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="4.0.2" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,424 @@
|
|||
using AyCode.Core.Extensions;
|
||||
using AyCode.Core.Tests.TestModels;
|
||||
|
||||
namespace AyCode.Core.Tests.Serialization;
|
||||
|
||||
[TestClass]
|
||||
public class AcBinarySerializerTests
|
||||
{
|
||||
#region Basic Serialization Tests
|
||||
|
||||
[TestMethod]
|
||||
public void Serialize_Null_ReturnsSingleNullByte()
|
||||
{
|
||||
var result = AcBinarySerializer.Serialize<object?>(null);
|
||||
Assert.AreEqual(1, result.Length);
|
||||
Assert.AreEqual((byte)32, result[0]); // BinaryTypeCode.Null = 32
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Serialize_Int32_RoundTrip()
|
||||
{
|
||||
var value = 12345;
|
||||
var binary = AcBinarySerializer.Serialize(value);
|
||||
var result = AcBinaryDeserializer.Deserialize<int>(binary);
|
||||
Assert.AreEqual(value, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Serialize_Int64_RoundTrip()
|
||||
{
|
||||
var value = 123456789012345L;
|
||||
var binary = AcBinarySerializer.Serialize(value);
|
||||
var result = AcBinaryDeserializer.Deserialize<long>(binary);
|
||||
Assert.AreEqual(value, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Serialize_Double_RoundTrip()
|
||||
{
|
||||
var value = 3.14159265358979;
|
||||
var binary = AcBinarySerializer.Serialize(value);
|
||||
var result = AcBinaryDeserializer.Deserialize<double>(binary);
|
||||
Assert.AreEqual(value, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Serialize_String_RoundTrip()
|
||||
{
|
||||
var value = "Hello, Binary World!";
|
||||
var binary = AcBinarySerializer.Serialize(value);
|
||||
var result = AcBinaryDeserializer.Deserialize<string>(binary);
|
||||
Assert.AreEqual(value, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Serialize_Boolean_RoundTrip()
|
||||
{
|
||||
var trueResult = AcBinaryDeserializer.Deserialize<bool>(AcBinarySerializer.Serialize(true));
|
||||
var falseResult = AcBinaryDeserializer.Deserialize<bool>(AcBinarySerializer.Serialize(false));
|
||||
Assert.IsTrue(trueResult);
|
||||
Assert.IsFalse(falseResult);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Serialize_DateTime_RoundTrip()
|
||||
{
|
||||
var value = new DateTime(2024, 12, 25, 10, 30, 45, DateTimeKind.Utc);
|
||||
var binary = AcBinarySerializer.Serialize(value);
|
||||
var result = AcBinaryDeserializer.Deserialize<DateTime>(binary);
|
||||
Assert.AreEqual(value, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Serialize_Guid_RoundTrip()
|
||||
{
|
||||
var value = Guid.NewGuid();
|
||||
var binary = AcBinarySerializer.Serialize(value);
|
||||
var result = AcBinaryDeserializer.Deserialize<Guid>(binary);
|
||||
Assert.AreEqual(value, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Serialize_Decimal_RoundTrip()
|
||||
{
|
||||
var value = 123456.789012m;
|
||||
var binary = AcBinarySerializer.Serialize(value);
|
||||
var result = AcBinaryDeserializer.Deserialize<decimal>(binary);
|
||||
Assert.AreEqual(value, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Serialize_TimeSpan_RoundTrip()
|
||||
{
|
||||
var value = TimeSpan.FromHours(2.5);
|
||||
var binary = AcBinarySerializer.Serialize(value);
|
||||
var result = AcBinaryDeserializer.Deserialize<TimeSpan>(binary);
|
||||
Assert.AreEqual(value, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Serialize_DateTimeOffset_RoundTrip()
|
||||
{
|
||||
var value = new DateTimeOffset(2024, 12, 25, 10, 30, 45, TimeSpan.FromHours(2));
|
||||
var binary = AcBinarySerializer.Serialize(value);
|
||||
var result = AcBinaryDeserializer.Deserialize<DateTimeOffset>(binary);
|
||||
|
||||
// Compare UTC ticks and offset separately since we store UTC ticks
|
||||
Assert.AreEqual(value.UtcTicks, result.UtcTicks);
|
||||
Assert.AreEqual(value.Offset, result.Offset);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Object Serialization Tests
|
||||
|
||||
[TestMethod]
|
||||
public void Serialize_SimpleObject_RoundTrip()
|
||||
{
|
||||
var obj = new TestSimpleClass
|
||||
{
|
||||
Id = 42,
|
||||
Name = "Test Object",
|
||||
Value = 3.14,
|
||||
IsActive = true
|
||||
};
|
||||
|
||||
var binary = obj.ToBinary();
|
||||
var result = binary.BinaryTo<TestSimpleClass>();
|
||||
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(obj.Id, result.Id);
|
||||
Assert.AreEqual(obj.Name, result.Name);
|
||||
Assert.AreEqual(obj.Value, result.Value);
|
||||
Assert.AreEqual(obj.IsActive, result.IsActive);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Serialize_NestedObject_RoundTrip()
|
||||
{
|
||||
var obj = new TestNestedClass
|
||||
{
|
||||
Id = 1,
|
||||
Name = "Parent",
|
||||
Child = new TestSimpleClass
|
||||
{
|
||||
Id = 2,
|
||||
Name = "Child",
|
||||
Value = 2.5,
|
||||
IsActive = true
|
||||
}
|
||||
};
|
||||
|
||||
var binary = obj.ToBinary();
|
||||
var result = binary.BinaryTo<TestNestedClass>();
|
||||
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(obj.Id, result.Id);
|
||||
Assert.AreEqual(obj.Name, result.Name);
|
||||
Assert.IsNotNull(result.Child);
|
||||
Assert.AreEqual(obj.Child.Id, result.Child.Id);
|
||||
Assert.AreEqual(obj.Child.Name, result.Child.Name);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Serialize_List_RoundTrip()
|
||||
{
|
||||
var list = new List<int> { 1, 2, 3, 4, 5 };
|
||||
var binary = list.ToBinary();
|
||||
var result = binary.BinaryTo<List<int>>();
|
||||
|
||||
Assert.IsNotNull(result);
|
||||
CollectionAssert.AreEqual(list, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Serialize_ObjectWithList_RoundTrip()
|
||||
{
|
||||
var obj = new TestClassWithList
|
||||
{
|
||||
Id = 1,
|
||||
Items = new List<string> { "Item1", "Item2", "Item3" }
|
||||
};
|
||||
|
||||
var binary = obj.ToBinary();
|
||||
var result = binary.BinaryTo<TestClassWithList>();
|
||||
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(obj.Id, result.Id);
|
||||
Assert.IsNotNull(result.Items);
|
||||
CollectionAssert.AreEqual(obj.Items, result.Items);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Serialize_Dictionary_RoundTrip()
|
||||
{
|
||||
var dict = new Dictionary<string, int>
|
||||
{
|
||||
["one"] = 1,
|
||||
["two"] = 2,
|
||||
["three"] = 3
|
||||
};
|
||||
|
||||
var binary = dict.ToBinary();
|
||||
var result = binary.BinaryTo<Dictionary<string, int>>();
|
||||
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(dict.Count, result.Count);
|
||||
foreach (var kvp in dict)
|
||||
{
|
||||
Assert.IsTrue(result.ContainsKey(kvp.Key));
|
||||
Assert.AreEqual(kvp.Value, result[kvp.Key]);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Populate Tests
|
||||
|
||||
[TestMethod]
|
||||
public void Populate_UpdatesExistingObject()
|
||||
{
|
||||
var target = new TestSimpleClass { Id = 0, Name = "Original" };
|
||||
var source = new TestSimpleClass { Id = 42, Name = "Updated", Value = 3.14 };
|
||||
|
||||
var binary = source.ToBinary();
|
||||
binary.BinaryTo(target);
|
||||
|
||||
Assert.AreEqual(42, target.Id);
|
||||
Assert.AreEqual("Updated", target.Name);
|
||||
Assert.AreEqual(3.14, target.Value);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PopulateMerge_MergesNestedObjects()
|
||||
{
|
||||
var target = new TestNestedClass
|
||||
{
|
||||
Id = 1,
|
||||
Name = "Original",
|
||||
Child = new TestSimpleClass { Id = 10, Name = "OriginalChild", Value = 1.0 }
|
||||
};
|
||||
|
||||
var source = new TestNestedClass
|
||||
{
|
||||
Id = 2,
|
||||
Name = "Updated",
|
||||
Child = new TestSimpleClass { Id = 20, Name = "UpdatedChild", Value = 2.0 }
|
||||
};
|
||||
|
||||
var binary = source.ToBinary();
|
||||
binary.BinaryToMerge(target);
|
||||
|
||||
Assert.AreEqual(2, target.Id);
|
||||
Assert.AreEqual("Updated", target.Name);
|
||||
Assert.IsNotNull(target.Child);
|
||||
// Child object should be merged, not replaced
|
||||
Assert.AreEqual(20, target.Child.Id);
|
||||
Assert.AreEqual("UpdatedChild", target.Child.Name);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region String Interning Tests
|
||||
|
||||
[TestMethod]
|
||||
public void Serialize_RepeatedStrings_UsesInterning()
|
||||
{
|
||||
var obj = new TestClassWithRepeatedStrings
|
||||
{
|
||||
Field1 = "Repeated",
|
||||
Field2 = "Repeated",
|
||||
Field3 = "Repeated",
|
||||
Field4 = "Unique"
|
||||
};
|
||||
|
||||
var binaryWithInterning = AcBinarySerializer.Serialize(obj, AcBinarySerializerOptions.Default);
|
||||
var binaryWithoutInterning = AcBinarySerializer.Serialize(obj,
|
||||
new AcBinarySerializerOptions { UseStringInterning = false });
|
||||
|
||||
// With interning should be smaller
|
||||
Assert.IsTrue(binaryWithInterning.Length < binaryWithoutInterning.Length,
|
||||
$"With interning: {binaryWithInterning.Length}, Without: {binaryWithoutInterning.Length}");
|
||||
|
||||
// Both should deserialize correctly
|
||||
var result1 = AcBinaryDeserializer.Deserialize<TestClassWithRepeatedStrings>(binaryWithInterning);
|
||||
var result2 = AcBinaryDeserializer.Deserialize<TestClassWithRepeatedStrings>(binaryWithoutInterning);
|
||||
|
||||
Assert.AreEqual(obj.Field1, result1!.Field1);
|
||||
Assert.AreEqual(obj.Field1, result2!.Field1);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Size Comparison Tests
|
||||
|
||||
[TestMethod]
|
||||
public void Serialize_IsSmallerThanJson()
|
||||
{
|
||||
var obj = TestDataFactory.CreateBenchmarkOrder(itemCount: 3, palletsPerItem: 2, measurementsPerPallet: 2, pointsPerMeasurement: 3);
|
||||
|
||||
var jsonBytes = System.Text.Encoding.UTF8.GetBytes(obj.ToJson());
|
||||
var binaryBytes = obj.ToBinary();
|
||||
|
||||
Console.WriteLine($"JSON size: {jsonBytes.Length} bytes");
|
||||
Console.WriteLine($"Binary size: {binaryBytes.Length} bytes");
|
||||
Console.WriteLine($"Ratio: {(double)binaryBytes.Length / jsonBytes.Length:P2}");
|
||||
|
||||
Assert.IsTrue(binaryBytes.Length < jsonBytes.Length,
|
||||
$"Binary ({binaryBytes.Length}) should be smaller than JSON ({jsonBytes.Length})");
|
||||
|
||||
// Verify roundtrip works
|
||||
var result = binaryBytes.BinaryTo<TestOrder>();
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(obj.Id, result.Id);
|
||||
Assert.AreEqual(obj.Items.Count, result.Items.Count);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extension Method Tests
|
||||
|
||||
[TestMethod]
|
||||
public void BinaryCloneTo_CreatesDeepCopy()
|
||||
{
|
||||
var original = new TestNestedClass
|
||||
{
|
||||
Id = 1,
|
||||
Name = "Original",
|
||||
Child = new TestSimpleClass { Id = 2, Name = "Child" }
|
||||
};
|
||||
|
||||
var clone = original.BinaryCloneTo();
|
||||
|
||||
Assert.IsNotNull(clone);
|
||||
Assert.AreNotSame(original, clone);
|
||||
Assert.AreNotSame(original.Child, clone.Child);
|
||||
Assert.AreEqual(original.Id, clone.Id);
|
||||
Assert.AreEqual(original.Child.Id, clone.Child!.Id);
|
||||
|
||||
// Modify clone, original should be unchanged
|
||||
clone.Id = 999;
|
||||
clone.Child.Id = 888;
|
||||
Assert.AreEqual(1, original.Id);
|
||||
Assert.AreEqual(2, original.Child.Id);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Test Models
|
||||
|
||||
private class TestSimpleClass
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = "";
|
||||
public double Value { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
}
|
||||
|
||||
private class TestNestedClass
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = "";
|
||||
public TestSimpleClass? Child { get; set; }
|
||||
}
|
||||
|
||||
private class TestClassWithList
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public List<string> Items { get; set; } = new();
|
||||
}
|
||||
|
||||
private class TestClassWithRepeatedStrings
|
||||
{
|
||||
public string Field1 { get; set; } = "";
|
||||
public string Field2 { get; set; } = "";
|
||||
public string Field3 { get; set; } = "";
|
||||
public string Field4 { get; set; } = "";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Benchmark Order Tests
|
||||
|
||||
[TestMethod]
|
||||
public void Serialize_BenchmarkOrder_RoundTrip()
|
||||
{
|
||||
// This is the exact same data that causes stack overflow in benchmarks
|
||||
var order = TestDataFactory.CreateBenchmarkOrder(
|
||||
itemCount: 3,
|
||||
palletsPerItem: 2,
|
||||
measurementsPerPallet: 2,
|
||||
pointsPerMeasurement: 5);
|
||||
|
||||
// Should not throw stack overflow
|
||||
var binary = AcBinarySerializer.Serialize(order);
|
||||
Assert.IsTrue(binary.Length > 0, "Binary data should not be empty");
|
||||
|
||||
var result = AcBinaryDeserializer.Deserialize<TestOrder>(binary);
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(order.Id, result.Id);
|
||||
Assert.AreEqual(order.OrderNumber, result.OrderNumber);
|
||||
Assert.AreEqual(order.Items.Count, result.Items.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Serialize_BenchmarkOrder_SmallData_RoundTrip()
|
||||
{
|
||||
// Smaller test to isolate the issue
|
||||
var order = TestDataFactory.CreateBenchmarkOrder(
|
||||
itemCount: 1,
|
||||
palletsPerItem: 1,
|
||||
measurementsPerPallet: 1,
|
||||
pointsPerMeasurement: 1);
|
||||
|
||||
var binary = AcBinarySerializer.Serialize(order);
|
||||
Assert.IsTrue(binary.Length > 0);
|
||||
|
||||
var result = AcBinaryDeserializer.Deserialize<TestOrder>(binary);
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(order.Id, result.Id);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
using AyCode.Core.Extensions;
|
||||
using AyCode.Core.Interfaces;
|
||||
using MessagePack;
|
||||
using MongoDB.Bson.Serialization.Attributes;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace AyCode.Core.Tests.TestModels;
|
||||
|
|
@ -159,8 +161,10 @@ public class TestOrder : IId<int>
|
|||
[JsonNoMergeCollection]
|
||||
public List<TestOrderItem> NoMergeItems { get; set; } = [];
|
||||
|
||||
// Parent reference (JsonIgnore to prevent loops)
|
||||
// Parent reference - ignored by all serializers to prevent circular references
|
||||
[JsonIgnore]
|
||||
[IgnoreMember]
|
||||
[BsonIgnore]
|
||||
public object? Parent { get; set; }
|
||||
}
|
||||
|
||||
|
|
@ -183,7 +187,10 @@ public class TestOrderItem : IId<int>
|
|||
public SharedUser? Assignee { get; set; }
|
||||
public MetadataInfo? ItemMetadata { get; set; }
|
||||
|
||||
// Parent reference - ignored by all serializers to prevent circular references
|
||||
[JsonIgnore]
|
||||
[IgnoreMember]
|
||||
[BsonIgnore]
|
||||
public TestOrder? ParentOrder { get; set; }
|
||||
}
|
||||
|
||||
|
|
@ -204,7 +211,10 @@ public class TestPallet : IId<int>
|
|||
// Shared references
|
||||
public MetadataInfo? PalletMetadata { get; set; }
|
||||
|
||||
// Parent reference - ignored by all serializers to prevent circular references
|
||||
[JsonIgnore]
|
||||
[IgnoreMember]
|
||||
[BsonIgnore]
|
||||
public TestOrderItem? ParentItem { get; set; }
|
||||
}
|
||||
|
||||
|
|
@ -221,7 +231,10 @@ public class TestMeasurement : IId<int>
|
|||
// Level 5 collection
|
||||
public List<TestMeasurementPoint> Points { get; set; } = [];
|
||||
|
||||
// Parent reference - ignored by all serializers to prevent circular references
|
||||
[JsonIgnore]
|
||||
[IgnoreMember]
|
||||
[BsonIgnore]
|
||||
public TestPallet? ParentPallet { get; set; }
|
||||
}
|
||||
|
||||
|
|
@ -235,7 +248,10 @@ public class TestMeasurementPoint : IId<int>
|
|||
public double Value { get; set; }
|
||||
public DateTime MeasuredAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
// Parent reference - ignored by all serializers to prevent circular references
|
||||
[JsonIgnore]
|
||||
[IgnoreMember]
|
||||
[BsonIgnore]
|
||||
public TestMeasurement? ParentMeasurement { get; set; }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
namespace AyCode.Core.Extensions;
|
||||
|
||||
public class AcBinarySerializer
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -489,7 +489,6 @@ public static class AcJsonDeserializer
|
|||
|
||||
if (instance == null) return null;
|
||||
|
||||
var propsDict = metadata.PropertySettersFrozen;
|
||||
var nextDepth = depth + 1;
|
||||
|
||||
while (reader.Read())
|
||||
|
|
@ -500,17 +499,22 @@ public static class AcJsonDeserializer
|
|||
if (reader.TokenType != JsonTokenType.PropertyName)
|
||||
continue;
|
||||
|
||||
var propName = reader.GetString();
|
||||
if (propName == null || !reader.Read())
|
||||
continue;
|
||||
|
||||
if (!propsDict.TryGetValue(propName, out var propInfo))
|
||||
// Use UTF8 lookup to avoid string allocation
|
||||
if (!metadata.TryGetPropertyUtf8(ref reader, out var propInfo) || propInfo == null)
|
||||
{
|
||||
if (!reader.Read()) break;
|
||||
reader.Skip();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use cached version for faster type resolution
|
||||
if (!reader.Read())
|
||||
break;
|
||||
|
||||
// Try direct set for primitives (no boxing)
|
||||
if (propInfo.TrySetValueDirect(instance, ref reader))
|
||||
continue;
|
||||
|
||||
// Fallback to boxed path for complex types
|
||||
var value = ReadValueFromReaderCached(ref reader, propInfo, maxDepth, nextDepth);
|
||||
propInfo.SetValue(instance, value);
|
||||
}
|
||||
|
|
@ -543,7 +547,6 @@ public static class AcJsonDeserializer
|
|||
|
||||
if (instance == null) return null;
|
||||
|
||||
var propsDict = metadata.PropertySettersFrozen;
|
||||
var nextDepth = depth + 1;
|
||||
|
||||
while (reader.Read())
|
||||
|
|
@ -554,17 +557,22 @@ public static class AcJsonDeserializer
|
|||
if (reader.TokenType != JsonTokenType.PropertyName)
|
||||
continue;
|
||||
|
||||
var propName = reader.GetString();
|
||||
if (propName == null || !reader.Read())
|
||||
continue;
|
||||
|
||||
if (!propsDict.TryGetValue(propName, out var propInfo))
|
||||
// Use UTF8 lookup to avoid string allocation
|
||||
if (!metadata.TryGetPropertyUtf8(ref reader, out var propInfo) || propInfo == null)
|
||||
{
|
||||
if (!reader.Read()) break;
|
||||
reader.Skip();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use cached version for faster type resolution
|
||||
if (!reader.Read())
|
||||
break;
|
||||
|
||||
// Try direct set for primitives (no boxing)
|
||||
if (propInfo.TrySetValueDirect(instance, ref reader))
|
||||
continue;
|
||||
|
||||
// Fallback to boxed path for complex types
|
||||
var value = ReadValueFromReaderCached(ref reader, propInfo, maxDepth, nextDepth);
|
||||
propInfo.SetValue(instance, value);
|
||||
}
|
||||
|
|
@ -706,7 +714,6 @@ public static class AcJsonDeserializer
|
|||
/// </summary>
|
||||
private static void PopulateObjectMergeFromReader(ref Utf8JsonReader reader, object target, DeserializeTypeMetadata metadata, byte maxDepth, int depth)
|
||||
{
|
||||
var propsDict = metadata.PropertySettersFrozen;
|
||||
var nextDepth = depth + 1;
|
||||
var maxDepthReached = nextDepth > maxDepth;
|
||||
|
||||
|
|
@ -718,25 +725,29 @@ public static class AcJsonDeserializer
|
|||
if (reader.TokenType != JsonTokenType.PropertyName)
|
||||
continue;
|
||||
|
||||
var propName = reader.GetString();
|
||||
if (propName == null || !reader.Read())
|
||||
continue;
|
||||
|
||||
if (!propsDict.TryGetValue(propName, out var propInfo))
|
||||
// Use UTF8 lookup to avoid string allocation
|
||||
if (!metadata.TryGetPropertyUtf8(ref reader, out var propInfo) || propInfo == null)
|
||||
{
|
||||
if (!reader.Read()) break;
|
||||
reader.Skip();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!reader.Read())
|
||||
break;
|
||||
|
||||
var tokenType = reader.TokenType;
|
||||
|
||||
if (maxDepthReached)
|
||||
{
|
||||
if (tokenType != JsonTokenType.StartObject && tokenType != JsonTokenType.StartArray)
|
||||
{
|
||||
// Use cached version for faster primitive reading
|
||||
var primitiveValue = ReadPrimitiveFromReaderCached(ref reader, propInfo);
|
||||
propInfo.SetValue(target, primitiveValue);
|
||||
// Try direct set for primitives (no boxing)
|
||||
if (!propInfo.TrySetValueDirect(target, ref reader))
|
||||
{
|
||||
var primitiveValue = ReadPrimitiveFromReaderCached(ref reader, propInfo);
|
||||
propInfo.SetValue(target, primitiveValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -768,7 +779,11 @@ public static class AcJsonDeserializer
|
|||
}
|
||||
}
|
||||
|
||||
// Use cached version for faster type resolution
|
||||
// Try direct set for primitives (no boxing)
|
||||
if (propInfo.TrySetValueDirect(target, ref reader))
|
||||
continue;
|
||||
|
||||
// Fallback to boxed path for complex types
|
||||
var value = ReadValueFromReaderCached(ref reader, propInfo, maxDepth, nextDepth);
|
||||
propInfo.SetValue(target, value);
|
||||
}
|
||||
|
|
@ -1401,6 +1416,7 @@ public static class AcJsonDeserializer
|
|||
private sealed class DeserializeTypeMetadata
|
||||
{
|
||||
public FrozenDictionary<string, PropertySetterInfo> PropertySettersFrozen { get; }
|
||||
public PropertySetterInfo[] PropertiesArray { get; } // Array for fast UTF8 linear scan (small objects)
|
||||
public Func<object>? CompiledConstructor { get; }
|
||||
|
||||
public DeserializeTypeMetadata(Type type)
|
||||
|
|
@ -1424,15 +1440,39 @@ public static class AcJsonDeserializer
|
|||
}
|
||||
|
||||
var propertySetters = new Dictionary<string, PropertySetterInfo>(propsList.Count, StringComparer.OrdinalIgnoreCase);
|
||||
var propsArray = new PropertySetterInfo[propsList.Count];
|
||||
var index = 0;
|
||||
|
||||
foreach (var prop in propsList)
|
||||
{
|
||||
propertySetters[prop.Name] = new PropertySetterInfo(prop, type);
|
||||
var propInfo = new PropertySetterInfo(prop, type);
|
||||
propertySetters[prop.Name] = propInfo;
|
||||
propsArray[index++] = propInfo;
|
||||
}
|
||||
|
||||
PropertiesArray = propsArray;
|
||||
// Create frozen dictionary for faster lookup in hot paths
|
||||
PropertySettersFrozen = propertySetters.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to find property by UTF-8 name using ValueTextEquals (avoids string allocation).
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGetPropertyUtf8(ref Utf8JsonReader reader, out PropertySetterInfo? propInfo)
|
||||
{
|
||||
var props = PropertiesArray;
|
||||
for (var i = 0; i < props.Length; i++)
|
||||
{
|
||||
if (reader.ValueTextEquals(props[i].NameUtf8))
|
||||
{
|
||||
propInfo = props[i];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
propInfo = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class PropertySetterInfo
|
||||
|
|
@ -1445,9 +1485,21 @@ public static class AcJsonDeserializer
|
|||
public readonly Type? ElementType;
|
||||
public readonly Type? ElementIdType;
|
||||
public readonly Func<object, object?>? ElementIdGetter;
|
||||
public readonly byte[] NameUtf8; // Pre-computed UTF-8 bytes of property name for fast matching
|
||||
|
||||
// Typed setters to avoid boxing for primitives
|
||||
private readonly Action<object, object?> _setter;
|
||||
private readonly Func<object, object?> _getter;
|
||||
|
||||
// Typed setters for common primitive types (avoid boxing)
|
||||
internal readonly Action<object, int>? _setInt32;
|
||||
internal readonly Action<object, long>? _setInt64;
|
||||
internal readonly Action<object, double>? _setDouble;
|
||||
internal readonly Action<object, bool>? _setBool;
|
||||
internal readonly Action<object, decimal>? _setDecimal;
|
||||
internal readonly Action<object, float>? _setSingle;
|
||||
internal readonly Action<object, DateTime>? _setDateTime;
|
||||
internal readonly Action<object, Guid>? _setGuid;
|
||||
|
||||
public PropertySetterInfo(PropertyInfo prop, Type declaringType)
|
||||
{
|
||||
|
|
@ -1456,9 +1508,31 @@ public static class AcJsonDeserializer
|
|||
IsNullable = underlying != null;
|
||||
UnderlyingType = underlying ?? PropertyType;
|
||||
PropertyTypeCode = Type.GetTypeCode(UnderlyingType);
|
||||
NameUtf8 = Encoding.UTF8.GetBytes(prop.Name);
|
||||
|
||||
_setter = CreateCompiledSetter(declaringType, prop);
|
||||
_getter = CreateCompiledGetter(declaringType, prop);
|
||||
|
||||
// Create typed setters for common primitives to avoid boxing
|
||||
if (!IsNullable)
|
||||
{
|
||||
if (ReferenceEquals(PropertyType, IntType))
|
||||
_setInt32 = CreateTypedSetter<int>(declaringType, prop);
|
||||
else if (ReferenceEquals(PropertyType, LongType))
|
||||
_setInt64 = CreateTypedSetter<long>(declaringType, prop);
|
||||
else if (ReferenceEquals(PropertyType, DoubleType))
|
||||
_setDouble = CreateTypedSetter<double>(declaringType, prop);
|
||||
else if (ReferenceEquals(PropertyType, BoolType))
|
||||
_setBool = CreateTypedSetter<bool>(declaringType, prop);
|
||||
else if (ReferenceEquals(PropertyType, DecimalType))
|
||||
_setDecimal = CreateTypedSetter<decimal>(declaringType, prop);
|
||||
else if (ReferenceEquals(PropertyType, FloatType))
|
||||
_setSingle = CreateTypedSetter<float>(declaringType, prop);
|
||||
else if (ReferenceEquals(PropertyType, DateTimeType))
|
||||
_setDateTime = CreateTypedSetter<DateTime>(declaringType, prop);
|
||||
else if (ReferenceEquals(PropertyType, GuidType))
|
||||
_setGuid = CreateTypedSetter<Guid>(declaringType, prop);
|
||||
}
|
||||
|
||||
ElementType = GetCollectionElementType(PropertyType);
|
||||
var isCollection = ElementType != null && ElementType != typeof(object) &&
|
||||
|
|
@ -1498,14 +1572,88 @@ public static class AcJsonDeserializer
|
|||
var boxed = Expression.Convert(propAccess, typeof(object));
|
||||
return Expression.Lambda<Func<object, object?>>(boxed, objParam).Compile();
|
||||
}
|
||||
|
||||
private static Action<object, T> CreateTypedSetter<T>(Type declaringType, PropertyInfo prop)
|
||||
{
|
||||
var objParam = Expression.Parameter(typeof(object), "obj");
|
||||
var valueParam = Expression.Parameter(typeof(T), "value");
|
||||
var castObj = Expression.Convert(objParam, declaringType);
|
||||
var propAccess = Expression.Property(castObj, prop);
|
||||
var assign = Expression.Assign(propAccess, valueParam);
|
||||
return Expression.Lambda<Action<object, T>>(assign, objParam, valueParam).Compile();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetValue(object target, object? value) => _setter(target, value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public object? GetValue(object target) => _getter(target);
|
||||
|
||||
/// <summary>
|
||||
/// Read and set value directly from Utf8JsonReader, avoiding boxing for primitives.
|
||||
/// Returns true if value was set, false if it needs fallback to SetValue.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TrySetValueDirect(object target, ref Utf8JsonReader reader)
|
||||
{
|
||||
var tokenType = reader.TokenType;
|
||||
|
||||
// Handle null
|
||||
if (tokenType == JsonTokenType.Null)
|
||||
{
|
||||
if (IsNullable || !PropertyType.IsValueType)
|
||||
{
|
||||
_setter(target, null);
|
||||
return true;
|
||||
}
|
||||
return true; // Skip null for non-nullable value types
|
||||
}
|
||||
|
||||
// Fast path for booleans - no boxing needed with typed setter
|
||||
if (tokenType == JsonTokenType.True)
|
||||
{
|
||||
if (_setBool != null) { _setBool(target, true); return true; }
|
||||
_setter(target, BoxedTrue);
|
||||
return true;
|
||||
}
|
||||
if (tokenType == JsonTokenType.False)
|
||||
{
|
||||
if (_setBool != null) { _setBool(target, false); return true; }
|
||||
_setter(target, BoxedFalse);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fast path for numbers - use typed setters when available
|
||||
if (tokenType == JsonTokenType.Number)
|
||||
{
|
||||
if (_setInt32 != null) { _setInt32(target, reader.GetInt32()); return true; }
|
||||
if (_setInt64 != null) { _setInt64(target, reader.GetInt64()); return true; }
|
||||
if (_setDouble != null) { _setDouble(target, reader.GetDouble()); return true; }
|
||||
if (_setDecimal != null) { _setDecimal(target, reader.GetDecimal()); return true; }
|
||||
if (_setSingle != null) { _setSingle(target, reader.GetSingle()); return true; }
|
||||
return false; // Fallback to boxed path
|
||||
}
|
||||
|
||||
// Fast path for strings - common types
|
||||
if (tokenType == JsonTokenType.String)
|
||||
{
|
||||
if (ReferenceEquals(UnderlyingType, StringType))
|
||||
{
|
||||
_setter(target, reader.GetString());
|
||||
return true;
|
||||
}
|
||||
if (_setDateTime != null) { _setDateTime(target, reader.GetDateTime()); return true; }
|
||||
if (_setGuid != null) { _setGuid(target, reader.GetGuid()); return true; }
|
||||
return false; // Fallback to boxed path
|
||||
}
|
||||
|
||||
return false; // Complex types need standard handling
|
||||
}
|
||||
|
||||
// Pre-boxed boolean values to avoid repeated boxing
|
||||
private static readonly object BoxedTrue = true;
|
||||
private static readonly object BoxedFalse = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Reference Resolution
|
||||
|
|
|
|||
|
|
@ -444,6 +444,65 @@ public static class SerializeObjectExtensions
|
|||
|
||||
public static T MessagePackTo<T>(this byte[] message, MessagePackSerializerOptions options)
|
||||
=> MessagePackSerializer.Deserialize<T>(message, options);
|
||||
|
||||
#region Binary Serialization Extension Methods
|
||||
|
||||
/// <summary>
|
||||
/// Serialize object to binary byte array with default options.
|
||||
/// Significantly faster than JSON, especially for large data in WASM.
|
||||
/// </summary>
|
||||
public static byte[] ToBinary<T>(this T source)
|
||||
=> AcBinarySerializer.Serialize(source);
|
||||
|
||||
/// <summary>
|
||||
/// Serialize object to binary byte array with specified options.
|
||||
/// </summary>
|
||||
public static byte[] ToBinary<T>(this T source, AcBinarySerializerOptions options)
|
||||
=> AcBinarySerializer.Serialize(source, options);
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize binary data to object with default options.
|
||||
/// </summary>
|
||||
public static T? BinaryTo<T>(this byte[] data)
|
||||
=> AcBinaryDeserializer.Deserialize<T>(data);
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize binary data to object.
|
||||
/// </summary>
|
||||
public static T? BinaryTo<T>(this ReadOnlySpan<byte> data)
|
||||
=> AcBinaryDeserializer.Deserialize<T>(data);
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize binary data to specified type.
|
||||
/// </summary>
|
||||
public static object? BinaryTo(this byte[] data, Type targetType)
|
||||
=> AcBinaryDeserializer.Deserialize(data.AsSpan(), targetType);
|
||||
|
||||
/// <summary>
|
||||
/// Populate existing object from binary data.
|
||||
/// </summary>
|
||||
public static void BinaryTo<T>(this byte[] data, T target) where T : class
|
||||
=> AcBinaryDeserializer.Populate(data, target);
|
||||
|
||||
/// <summary>
|
||||
/// Populate existing object from binary data with merge semantics for IId collections.
|
||||
/// </summary>
|
||||
public static void BinaryToMerge<T>(this byte[] data, T target) where T : class
|
||||
=> AcBinaryDeserializer.PopulateMerge(data.AsSpan(), target);
|
||||
|
||||
/// <summary>
|
||||
/// Clone object via binary serialization (faster than JSON clone).
|
||||
/// </summary>
|
||||
public static T? BinaryCloneTo<T>(this T source) where T : class
|
||||
=> source?.ToBinary().BinaryTo<T>();
|
||||
|
||||
/// <summary>
|
||||
/// Copy object properties to target via binary serialization.
|
||||
/// </summary>
|
||||
public static void BinaryCopyTo<T>(this T source, T target) where T : class
|
||||
=> source?.ToBinary().BinaryTo(target);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public class IgnoreAndRenamePropertySerializerContractResolver : DefaultContractResolver
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.15.2" />
|
||||
<PackageReference Include="MessagePack" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.DiagnosticsHub.BenchmarkDotNetDiagnosers" Version="18.3.36726.2" />
|
||||
<PackageReference Include="MongoDB.Bson" Version="3.5.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
using BenchmarkDotNet.Running;
|
||||
using AyCode.Core.Benchmarks;
|
||||
using AyCode.Core.Extensions;
|
||||
using AyCode.Core.Tests.TestModels;
|
||||
using System.Text;
|
||||
using MessagePack;
|
||||
using MessagePack.Resolvers;
|
||||
|
||||
namespace BenchmarkSuite1
|
||||
{
|
||||
|
|
@ -7,61 +12,167 @@ namespace BenchmarkSuite1
|
|||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// Quick size comparison test
|
||||
if (args.Length > 0 && args[0] == "--test")
|
||||
{
|
||||
RunQuickTest();
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length > 0 && args[0] == "--testmsgpack")
|
||||
{
|
||||
RunMessagePackTest();
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length > 0 && args[0] == "--minimal")
|
||||
{
|
||||
BenchmarkRunner.Run<MinimalBenchmark>();
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length > 0 && args[0] == "--simple")
|
||||
{
|
||||
BenchmarkRunner.Run<SimpleBinaryBenchmark>();
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length > 0 && args[0] == "--complex")
|
||||
{
|
||||
BenchmarkRunner.Run<ComplexBinaryBenchmark>();
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length > 0 && args[0] == "--msgpack")
|
||||
{
|
||||
BenchmarkRunner.Run<MessagePackComparisonBenchmark>();
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length > 0 && args[0] == "--sizes")
|
||||
{
|
||||
RunSizeComparison();
|
||||
return;
|
||||
}
|
||||
|
||||
// Use assembly-wide discovery for all benchmarks
|
||||
BenchmarkSwitcher.FromAssembly(typeof(SerializationBenchmarks).Assembly).Run(args);
|
||||
Console.WriteLine("Usage:");
|
||||
Console.WriteLine(" --test Quick AcBinary test");
|
||||
Console.WriteLine(" --testmsgpack Quick MessagePack test");
|
||||
Console.WriteLine(" --minimal Minimal benchmark");
|
||||
Console.WriteLine(" --simple Simple flat object benchmark");
|
||||
Console.WriteLine(" --complex Complex hierarchy (AcBinary vs JSON)");
|
||||
Console.WriteLine(" --msgpack MessagePack comparison");
|
||||
Console.WriteLine(" --sizes Size comparison only");
|
||||
|
||||
if (args.Length == 0)
|
||||
{
|
||||
BenchmarkSwitcher.FromAssembly(typeof(MinimalBenchmark).Assembly).Run(args);
|
||||
}
|
||||
else
|
||||
{
|
||||
BenchmarkSwitcher.FromAssembly(typeof(MinimalBenchmark).Assembly).Run(args);
|
||||
}
|
||||
}
|
||||
|
||||
static void RunQuickTest()
|
||||
{
|
||||
Console.WriteLine("=== Quick AcBinary Test ===\n");
|
||||
|
||||
try
|
||||
{
|
||||
Console.WriteLine("Creating test data...");
|
||||
var order = TestDataFactory.CreateBenchmarkOrder(
|
||||
itemCount: 3,
|
||||
palletsPerItem: 2,
|
||||
measurementsPerPallet: 2,
|
||||
pointsPerMeasurement: 5);
|
||||
Console.WriteLine($"Created order with {order.Items.Count} items");
|
||||
|
||||
Console.WriteLine("\nTesting JSON serialization...");
|
||||
var jsonOptions = AcJsonSerializerOptions.WithoutReferenceHandling();
|
||||
var json = AcJsonSerializer.Serialize(order, jsonOptions);
|
||||
Console.WriteLine($"JSON size: {json.Length:N0} chars, {Encoding.UTF8.GetByteCount(json):N0} bytes");
|
||||
|
||||
Console.WriteLine("\nTesting Binary serialization...");
|
||||
var binaryOptions = AcBinarySerializerOptions.Default;
|
||||
var binary = AcBinarySerializer.Serialize(order, binaryOptions);
|
||||
Console.WriteLine($"Binary size: {binary.Length:N0} bytes");
|
||||
|
||||
Console.WriteLine("\nTesting Binary deserialization...");
|
||||
var result = AcBinaryDeserializer.Deserialize<TestOrder>(binary);
|
||||
Console.WriteLine($"Deserialized order: Id={result?.Id}, Items={result?.Items.Count}");
|
||||
|
||||
Console.WriteLine("\n=== All tests passed! ===");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"\n!!! ERROR: {ex.GetType().Name}: {ex.Message}");
|
||||
Console.WriteLine(ex.StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
static void RunMessagePackTest()
|
||||
{
|
||||
Console.WriteLine("=== Quick MessagePack Test ===\n");
|
||||
|
||||
try
|
||||
{
|
||||
Console.WriteLine("Creating test data...");
|
||||
var order = TestDataFactory.CreateBenchmarkOrder(
|
||||
itemCount: 2,
|
||||
palletsPerItem: 2,
|
||||
measurementsPerPallet: 2,
|
||||
pointsPerMeasurement: 3);
|
||||
Console.WriteLine($"Created order with {order.Items.Count} items");
|
||||
|
||||
Console.WriteLine("\nTesting MessagePack serialization...");
|
||||
var msgPackOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
|
||||
var msgPack = MessagePackSerializer.Serialize(order, msgPackOptions);
|
||||
Console.WriteLine($"MessagePack size: {msgPack.Length:N0} bytes");
|
||||
|
||||
Console.WriteLine("\nTesting MessagePack deserialization...");
|
||||
var result = MessagePackSerializer.Deserialize<TestOrder>(msgPack, msgPackOptions);
|
||||
Console.WriteLine($"Deserialized order: Id={result?.Id}, Items={result?.Items.Count}");
|
||||
|
||||
Console.WriteLine("\n=== MessagePack test passed! ===");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"\n!!! ERROR: {ex.GetType().Name}: {ex.Message}");
|
||||
Console.WriteLine(ex.StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
static void RunSizeComparison()
|
||||
{
|
||||
Console.WriteLine("=== JSON Size Comparison ===\n");
|
||||
Console.WriteLine("=== Size Comparison ===\n");
|
||||
|
||||
var benchmark = new AyCode.Core.Benchmarks.SerializationBenchmarks();
|
||||
|
||||
// Manually invoke setup
|
||||
var setupMethod = typeof(AyCode.Core.Benchmarks.SerializationBenchmarks)
|
||||
.GetMethod("Setup");
|
||||
setupMethod?.Invoke(benchmark, null);
|
||||
|
||||
// Get JSON sizes via reflection (private fields)
|
||||
var newtonsoftJson = typeof(AyCode.Core.Benchmarks.SerializationBenchmarks)
|
||||
.GetField("_newtonsoftJson", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
|
||||
?.GetValue(benchmark) as string;
|
||||
|
||||
var ayCodeJson = typeof(AyCode.Core.Benchmarks.SerializationBenchmarks)
|
||||
.GetField("_ayCodeJson", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
|
||||
?.GetValue(benchmark) as string;
|
||||
|
||||
if (newtonsoftJson != null && ayCodeJson != null)
|
||||
var order = TestDataFactory.CreateBenchmarkOrder(
|
||||
itemCount: 3,
|
||||
palletsPerItem: 2,
|
||||
measurementsPerPallet: 2,
|
||||
pointsPerMeasurement: 5);
|
||||
|
||||
var binaryWithRef = AcBinarySerializer.Serialize(order, AcBinarySerializerOptions.Default);
|
||||
var binaryNoRef = AcBinarySerializer.Serialize(order, AcBinarySerializerOptions.WithoutReferenceHandling);
|
||||
var json = AcJsonSerializer.Serialize(order, AcJsonSerializerOptions.WithoutReferenceHandling());
|
||||
var jsonBytes = Encoding.UTF8.GetByteCount(json);
|
||||
|
||||
Console.WriteLine($"| Format | Size (bytes) | vs JSON |");
|
||||
Console.WriteLine($"|-----------------|--------------|---------|");
|
||||
Console.WriteLine($"| AcBinary | {binaryWithRef.Length,12:N0} | {100.0 * binaryWithRef.Length / jsonBytes,6:F1}% |");
|
||||
Console.WriteLine($"| AcBinary(NoRef) | {binaryNoRef.Length,12:N0} | {100.0 * binaryNoRef.Length / jsonBytes,6:F1}% |");
|
||||
Console.WriteLine($"| JSON | {jsonBytes,12:N0} | 100.0% |");
|
||||
|
||||
// Try MessagePack
|
||||
try
|
||||
{
|
||||
var newtonsoftBytes = System.Text.Encoding.UTF8.GetByteCount(newtonsoftJson);
|
||||
var ayCodeBytes = System.Text.Encoding.UTF8.GetByteCount(ayCodeJson);
|
||||
|
||||
Console.WriteLine($"Newtonsoft JSON (no refs):");
|
||||
Console.WriteLine($" - Characters: {newtonsoftJson.Length:N0}");
|
||||
Console.WriteLine($" - Bytes: {newtonsoftBytes:N0} ({newtonsoftBytes / 1024.0 / 1024.0:F2} MB)");
|
||||
Console.WriteLine();
|
||||
|
||||
Console.WriteLine($"AyCode JSON (with refs):");
|
||||
Console.WriteLine($" - Characters: {ayCodeJson.Length:N0}");
|
||||
Console.WriteLine($" - Bytes: {ayCodeBytes:N0} ({ayCodeBytes / 1024.0 / 1024.0:F2} MB)");
|
||||
Console.WriteLine();
|
||||
|
||||
var reduction = (1.0 - (double)ayCodeBytes / newtonsoftBytes) * 100;
|
||||
Console.WriteLine($"Size Reduction: {reduction:F1}%");
|
||||
Console.WriteLine($"AyCode is {(reduction > 0 ? "smaller" : "larger")} by {Math.Abs(newtonsoftBytes - ayCodeBytes):N0} bytes");
|
||||
|
||||
// Count $ref occurrences
|
||||
var refCount = System.Text.RegularExpressions.Regex.Matches(ayCodeJson, @"\$ref").Count;
|
||||
var idCount = System.Text.RegularExpressions.Regex.Matches(ayCodeJson, @"\$id").Count;
|
||||
Console.WriteLine($"\nAyCode $id count: {idCount}");
|
||||
Console.WriteLine($"AyCode $ref count: {refCount}");
|
||||
var msgPackOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
|
||||
var msgPack = MessagePackSerializer.Serialize(order, msgPackOptions);
|
||||
Console.WriteLine($"| MessagePack | {msgPack.Length,12:N0} | {100.0 * msgPack.Length / jsonBytes,6:F1}% |");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"| MessagePack | FAILED: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
using AyCode.Core.Extensions;
|
||||
using AyCode.Core.Tests.TestModels;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Columns;
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Reports;
|
||||
using Newtonsoft.Json;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using MessagePack;
|
||||
using MessagePack.Resolvers;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
|
@ -13,327 +12,185 @@ using JsonSerializer = System.Text.Json.JsonSerializer;
|
|||
namespace AyCode.Core.Benchmarks;
|
||||
|
||||
/// <summary>
|
||||
/// Serialization benchmarks comparing AyCode, Newtonsoft.Json, and System.Text.Json.
|
||||
/// Tests small, medium, and large data with and without reference handling.
|
||||
/// Minimal benchmark to test if BenchmarkDotNet works without stack overflow.
|
||||
/// </summary>
|
||||
[ShortRunJob]
|
||||
[MemoryDiagnoser]
|
||||
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
|
||||
[CategoriesColumn]
|
||||
public class SerializationBenchmarks
|
||||
public class MinimalBenchmark
|
||||
{
|
||||
// Test data - small, medium, large
|
||||
private TestOrder _smallOrder = null!;
|
||||
private TestOrder _mediumOrder = null!;
|
||||
private TestOrder _largeOrder = null!;
|
||||
|
||||
// Pre-serialized JSON for deserialization benchmarks
|
||||
private string _smallAyCodeJson = null!;
|
||||
private string _smallAyCodeNoRefJson = null!;
|
||||
private string _smallStjJson = null!;
|
||||
private string _smallStjNoRefJson = null!;
|
||||
private string _smallNewtonsoftJson = null!;
|
||||
private string _mediumAyCodeJson = null!;
|
||||
private string _mediumAyCodeNoRefJson = null!;
|
||||
private string _mediumStjJson = null!;
|
||||
private string _mediumStjNoRefJson = null!;
|
||||
private string _mediumNewtonsoftJson = null!;
|
||||
private string _largeAyCodeJson = null!;
|
||||
private string _largeAyCodeNoRefJson = null!;
|
||||
private string _largeStjJson = null!;
|
||||
private string _largeStjNoRefJson = null!;
|
||||
private string _largeNewtonsoftJson = null!;
|
||||
|
||||
// STJ options
|
||||
private JsonSerializerOptions _stjWithRefs = null!;
|
||||
private JsonSerializerOptions _stjNoRefs = null!;
|
||||
|
||||
// AyCode options
|
||||
private AcJsonSerializerOptions _ayCodeWithRefs = null!;
|
||||
private AcJsonSerializerOptions _ayCodeNoRefs = null!;
|
||||
|
||||
// Newtonsoft settings
|
||||
private JsonSerializerSettings _newtonsoftSettings = null!;
|
||||
private byte[] _data = null!;
|
||||
private string _json = null!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
// Small: ~20 objects (1 item × 1 pallet × 2 measurements × 3 points)
|
||||
_smallOrder = TestDataFactory.CreateBenchmarkOrder(
|
||||
itemCount: 1,
|
||||
palletsPerItem: 1,
|
||||
measurementsPerPallet: 2,
|
||||
pointsPerMeasurement: 3);
|
||||
// Use very simple data - no circular references
|
||||
var simpleData = new { Id = 1, Name = "Test", Value = 42.5 };
|
||||
_json = System.Text.Json.JsonSerializer.Serialize(simpleData);
|
||||
_data = Encoding.UTF8.GetBytes(_json);
|
||||
Console.WriteLine($"Setup complete. Data size: {_data.Length} bytes");
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int GetLength() => _data.Length;
|
||||
|
||||
[Benchmark]
|
||||
public string GetJson() => _json;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binary vs JSON benchmark with simple flat objects (no circular references).
|
||||
/// </summary>
|
||||
[ShortRunJob]
|
||||
[MemoryDiagnoser]
|
||||
public class SimpleBinaryBenchmark
|
||||
{
|
||||
private PrimitiveTestClass _testData = null!;
|
||||
private byte[] _binaryData = null!;
|
||||
private string _jsonData = null!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_testData = TestDataFactory.CreatePrimitiveTestData();
|
||||
_binaryData = AcBinarySerializer.Serialize(_testData);
|
||||
_jsonData = AcJsonSerializer.Serialize(_testData, AcJsonSerializerOptions.WithoutReferenceHandling());
|
||||
|
||||
// Medium: ~300 objects (3 items × 2 pallets × 2 measurements × 5 points)
|
||||
_mediumOrder = TestDataFactory.CreateBenchmarkOrder(
|
||||
itemCount: 3,
|
||||
Console.WriteLine($"Binary: {_binaryData.Length} bytes, JSON: {_jsonData.Length} chars");
|
||||
}
|
||||
|
||||
[Benchmark(Description = "Binary Serialize")]
|
||||
public byte[] SerializeBinary() => AcBinarySerializer.Serialize(_testData);
|
||||
|
||||
[Benchmark(Description = "JSON Serialize", Baseline = true)]
|
||||
public string SerializeJson() => AcJsonSerializer.Serialize(_testData, AcJsonSerializerOptions.WithoutReferenceHandling());
|
||||
|
||||
[Benchmark(Description = "Binary Deserialize")]
|
||||
public PrimitiveTestClass? DeserializeBinary() => AcBinaryDeserializer.Deserialize<PrimitiveTestClass>(_binaryData);
|
||||
|
||||
[Benchmark(Description = "JSON Deserialize")]
|
||||
public PrimitiveTestClass? DeserializeJson() => AcJsonDeserializer.Deserialize<PrimitiveTestClass>(_jsonData, AcJsonSerializerOptions.WithoutReferenceHandling());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Complex hierarchy benchmark - AcBinary vs JSON only (no MessagePack to isolate the issue).
|
||||
/// </summary>
|
||||
[ShortRunJob]
|
||||
[MemoryDiagnoser]
|
||||
[RankColumn]
|
||||
public class ComplexBinaryBenchmark
|
||||
{
|
||||
private TestOrder _testOrder = null!;
|
||||
private byte[] _acBinaryData = null!;
|
||||
private string _jsonData = null!;
|
||||
|
||||
private AcBinarySerializerOptions _binaryOptions = null!;
|
||||
private AcJsonSerializerOptions _jsonOptions = null!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
Console.WriteLine("Creating test data...");
|
||||
_testOrder = TestDataFactory.CreateBenchmarkOrder(
|
||||
itemCount: 2,
|
||||
palletsPerItem: 2,
|
||||
measurementsPerPallet: 2,
|
||||
pointsPerMeasurement: 5);
|
||||
pointsPerMeasurement: 3);
|
||||
Console.WriteLine($"Created order with {_testOrder.Items.Count} items");
|
||||
|
||||
_binaryOptions = AcBinarySerializerOptions.Default;
|
||||
_jsonOptions = AcJsonSerializerOptions.WithoutReferenceHandling();
|
||||
|
||||
Console.WriteLine("Serializing AcBinary...");
|
||||
_acBinaryData = AcBinarySerializer.Serialize(_testOrder, _binaryOptions);
|
||||
Console.WriteLine($"AcBinary size: {_acBinaryData.Length} bytes");
|
||||
|
||||
Console.WriteLine("Serializing JSON...");
|
||||
_jsonData = AcJsonSerializer.Serialize(_testOrder, _jsonOptions);
|
||||
Console.WriteLine($"JSON size: {_jsonData.Length} chars");
|
||||
|
||||
var jsonBytes = Encoding.UTF8.GetByteCount(_jsonData);
|
||||
Console.WriteLine($"\n=== SIZE COMPARISON ===");
|
||||
Console.WriteLine($"AcBinary: {_acBinaryData.Length,8:N0} bytes ({100.0 * _acBinaryData.Length / jsonBytes:F1}%)");
|
||||
Console.WriteLine($"JSON: {jsonBytes,8:N0} bytes (100.0%)");
|
||||
}
|
||||
|
||||
[Benchmark(Description = "AcBinary Serialize")]
|
||||
public byte[] Serialize_AcBinary() => AcBinarySerializer.Serialize(_testOrder, _binaryOptions);
|
||||
|
||||
[Benchmark(Description = "JSON Serialize", Baseline = true)]
|
||||
public string Serialize_Json() => AcJsonSerializer.Serialize(_testOrder, _jsonOptions);
|
||||
|
||||
[Benchmark(Description = "AcBinary Deserialize")]
|
||||
public TestOrder? Deserialize_AcBinary() => AcBinaryDeserializer.Deserialize<TestOrder>(_acBinaryData);
|
||||
|
||||
[Benchmark(Description = "JSON Deserialize")]
|
||||
public TestOrder? Deserialize_Json() => AcJsonDeserializer.Deserialize<TestOrder>(_jsonData, _jsonOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Full comparison with MessagePack - separate class to isolate potential issues.
|
||||
/// </summary>
|
||||
[ShortRunJob]
|
||||
[MemoryDiagnoser]
|
||||
[RankColumn]
|
||||
public class MessagePackComparisonBenchmark
|
||||
{
|
||||
private TestOrder _testOrder = null!;
|
||||
private byte[] _acBinaryData = null!;
|
||||
private byte[] _msgPackData = null!;
|
||||
private string _jsonData = null!;
|
||||
|
||||
private AcBinarySerializerOptions _binaryOptions = null!;
|
||||
private MessagePackSerializerOptions _msgPackOptions = null!;
|
||||
private AcJsonSerializerOptions _jsonOptions = null!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
Console.WriteLine("Creating test data...");
|
||||
_testOrder = TestDataFactory.CreateBenchmarkOrder(
|
||||
itemCount: 2,
|
||||
palletsPerItem: 2,
|
||||
measurementsPerPallet: 2,
|
||||
pointsPerMeasurement: 3);
|
||||
|
||||
_binaryOptions = AcBinarySerializerOptions.Default;
|
||||
_msgPackOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
|
||||
_jsonOptions = AcJsonSerializerOptions.WithoutReferenceHandling();
|
||||
|
||||
_acBinaryData = AcBinarySerializer.Serialize(_testOrder, _binaryOptions);
|
||||
_jsonData = AcJsonSerializer.Serialize(_testOrder, _jsonOptions);
|
||||
|
||||
// Large: ~1500 objects (5 items × 4 pallets × 3 measurements × 5 points)
|
||||
_largeOrder = TestDataFactory.CreateBenchmarkOrder(
|
||||
itemCount: 5,
|
||||
palletsPerItem: 4,
|
||||
measurementsPerPallet: 3,
|
||||
pointsPerMeasurement: 5);
|
||||
|
||||
// STJ options with reference handling
|
||||
_stjWithRefs = new JsonSerializerOptions
|
||||
// MessagePack serialization in try-catch to see if it fails
|
||||
try
|
||||
{
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
PropertyNamingPolicy = null,
|
||||
WriteIndented = false,
|
||||
ReferenceHandler = ReferenceHandler.Preserve,
|
||||
MaxDepth = 256
|
||||
};
|
||||
|
||||
// STJ options without reference handling
|
||||
_stjNoRefs = new JsonSerializerOptions
|
||||
Console.WriteLine("Serializing MessagePack...");
|
||||
_msgPackData = MessagePackSerializer.Serialize(_testOrder, _msgPackOptions);
|
||||
Console.WriteLine($"MessagePack size: {_msgPackData.Length} bytes");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
PropertyNamingPolicy = null,
|
||||
WriteIndented = false,
|
||||
ReferenceHandler = ReferenceHandler.IgnoreCycles,
|
||||
MaxDepth = 256
|
||||
};
|
||||
|
||||
// AyCode options
|
||||
_ayCodeWithRefs = AcJsonSerializerOptions.Default;
|
||||
_ayCodeNoRefs = AcJsonSerializerOptions.WithoutReferenceHandling();
|
||||
|
||||
// Newtonsoft settings
|
||||
_newtonsoftSettings = new JsonSerializerSettings
|
||||
{
|
||||
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
Formatting = Formatting.None
|
||||
};
|
||||
|
||||
// Pre-serialize for deserialization benchmarks
|
||||
_smallAyCodeJson = AcJsonSerializer.Serialize(_smallOrder, _ayCodeWithRefs);
|
||||
_smallAyCodeNoRefJson = AcJsonSerializer.Serialize(_smallOrder, _ayCodeNoRefs);
|
||||
_smallStjJson = JsonSerializer.Serialize(_smallOrder, _stjWithRefs);
|
||||
_smallStjNoRefJson = JsonSerializer.Serialize(_smallOrder, _stjNoRefs);
|
||||
_smallNewtonsoftJson = JsonConvert.SerializeObject(_smallOrder, _newtonsoftSettings);
|
||||
|
||||
_mediumAyCodeJson = AcJsonSerializer.Serialize(_mediumOrder, _ayCodeWithRefs);
|
||||
_mediumAyCodeNoRefJson = AcJsonSerializer.Serialize(_mediumOrder, _ayCodeNoRefs);
|
||||
_mediumStjJson = JsonSerializer.Serialize(_mediumOrder, _stjWithRefs);
|
||||
_mediumStjNoRefJson = JsonSerializer.Serialize(_mediumOrder, _stjNoRefs);
|
||||
_mediumNewtonsoftJson = JsonConvert.SerializeObject(_mediumOrder, _newtonsoftSettings);
|
||||
|
||||
_largeAyCodeJson = AcJsonSerializer.Serialize(_largeOrder, _ayCodeWithRefs);
|
||||
_largeAyCodeNoRefJson = AcJsonSerializer.Serialize(_largeOrder, _ayCodeNoRefs);
|
||||
_largeStjJson = JsonSerializer.Serialize(_largeOrder, _stjWithRefs);
|
||||
_largeStjNoRefJson = JsonSerializer.Serialize(_largeOrder, _stjNoRefs);
|
||||
_largeNewtonsoftJson = JsonConvert.SerializeObject(_largeOrder, _newtonsoftSettings);
|
||||
|
||||
// Output sizes for comparison
|
||||
Console.WriteLine("=== JSON Size Comparison ===");
|
||||
Console.WriteLine($"Small: AyCode(refs)={_smallAyCodeJson.Length:N0}, AyCode(noRef)={_smallAyCodeNoRefJson.Length:N0}, STJ(refs)={_smallStjJson.Length:N0}, STJ(noRef)={_smallStjNoRefJson.Length:N0}");
|
||||
Console.WriteLine($"Medium: AyCode(refs)={_mediumAyCodeJson.Length:N0}, AyCode(noRef)={_mediumAyCodeNoRefJson.Length:N0}, STJ(refs)={_mediumStjJson.Length:N0}, STJ(noRef)={_mediumStjNoRefJson.Length:N0}");
|
||||
Console.WriteLine($"Large: AyCode(refs)={_largeAyCodeJson.Length:N0}, AyCode(noRef)={_largeAyCodeNoRefJson.Length:N0}, STJ(refs)={_largeStjJson.Length:N0}, STJ(noRef)={_largeStjNoRefJson.Length:N0}");
|
||||
Console.WriteLine($"MessagePack serialization failed: {ex.Message}");
|
||||
_msgPackData = Array.Empty<byte>();
|
||||
}
|
||||
|
||||
var jsonBytes = Encoding.UTF8.GetByteCount(_jsonData);
|
||||
Console.WriteLine($"\n=== SIZE COMPARISON ===");
|
||||
Console.WriteLine($"AcBinary: {_acBinaryData.Length,8:N0} bytes ({100.0 * _acBinaryData.Length / jsonBytes:F1}%)");
|
||||
Console.WriteLine($"MessagePack: {_msgPackData.Length,8:N0} bytes ({100.0 * _msgPackData.Length / jsonBytes:F1}%)");
|
||||
Console.WriteLine($"JSON: {jsonBytes,8:N0} bytes (100.0%)");
|
||||
}
|
||||
|
||||
#region Serialize Large - With Refs
|
||||
[Benchmark(Description = "AcBinary Serialize")]
|
||||
public byte[] Serialize_AcBinary() => AcBinarySerializer.Serialize(_testOrder, _binaryOptions);
|
||||
|
||||
[Benchmark(Description = "AyCode Serialize")]
|
||||
[BenchmarkCategory("Serialize-Large-WithRefs")]
|
||||
public string Serialize_Large_AyCode_WithRefs()
|
||||
=> AcJsonSerializer.Serialize(_largeOrder, _ayCodeWithRefs);
|
||||
[Benchmark(Description = "MessagePack Serialize", Baseline = true)]
|
||||
public byte[] Serialize_MsgPack() => MessagePackSerializer.Serialize(_testOrder, _msgPackOptions);
|
||||
|
||||
[Benchmark(Description = "STJ Serialize", Baseline = true)]
|
||||
[BenchmarkCategory("Serialize-Large-WithRefs")]
|
||||
public string Serialize_Large_STJ_WithRefs()
|
||||
=> JsonSerializer.Serialize(_largeOrder, _stjWithRefs);
|
||||
[Benchmark(Description = "AcBinary Deserialize")]
|
||||
public TestOrder? Deserialize_AcBinary() => AcBinaryDeserializer.Deserialize<TestOrder>(_acBinaryData);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Serialize Large - No Refs
|
||||
|
||||
[Benchmark(Description = "AyCode Serialize")]
|
||||
[BenchmarkCategory("Serialize-Large-NoRefs")]
|
||||
public string Serialize_Large_AyCode_NoRefs()
|
||||
=> AcJsonSerializer.Serialize(_largeOrder, _ayCodeNoRefs);
|
||||
|
||||
[Benchmark(Description = "STJ Serialize", Baseline = true)]
|
||||
[BenchmarkCategory("Serialize-Large-NoRefs")]
|
||||
public string Serialize_Large_STJ_NoRefs()
|
||||
=> JsonSerializer.Serialize(_largeOrder, _stjNoRefs);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Serialize Medium - With Refs
|
||||
|
||||
[Benchmark(Description = "AyCode Serialize")]
|
||||
[BenchmarkCategory("Serialize-Medium-WithRefs")]
|
||||
public string Serialize_Medium_AyCode_WithRefs()
|
||||
=> AcJsonSerializer.Serialize(_mediumOrder, _ayCodeWithRefs);
|
||||
|
||||
[Benchmark(Description = "STJ Serialize", Baseline = true)]
|
||||
[BenchmarkCategory("Serialize-Medium-WithRefs")]
|
||||
public string Serialize_Medium_STJ_WithRefs()
|
||||
=> JsonSerializer.Serialize(_mediumOrder, _stjWithRefs);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Serialize Medium - No Refs
|
||||
|
||||
[Benchmark(Description = "AyCode Serialize")]
|
||||
[BenchmarkCategory("Serialize-Medium-NoRefs")]
|
||||
public string Serialize_Medium_AyCode_NoRefs()
|
||||
=> AcJsonSerializer.Serialize(_mediumOrder, _ayCodeNoRefs);
|
||||
|
||||
[Benchmark(Description = "STJ Serialize", Baseline = true)]
|
||||
[BenchmarkCategory("Serialize-Medium-NoRefs")]
|
||||
public string Serialize_Medium_STJ_NoRefs()
|
||||
=> JsonSerializer.Serialize(_mediumOrder, _stjNoRefs);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Small Data Deserialization - With Refs
|
||||
|
||||
[Benchmark(Description = "AyCode WithRefs")]
|
||||
[BenchmarkCategory("Deserialize-Small-WithRefs")]
|
||||
public TestOrder? Deserialize_Small_AyCode_WithRefs()
|
||||
=> AcJsonDeserializer.Deserialize<TestOrder>(_smallAyCodeJson, _ayCodeWithRefs);
|
||||
|
||||
[Benchmark(Description = "STJ WithRefs", Baseline = true)]
|
||||
[BenchmarkCategory("Deserialize-Small-WithRefs")]
|
||||
public TestOrder? Deserialize_Small_STJ_WithRefs()
|
||||
=> JsonSerializer.Deserialize<TestOrder>(_smallStjJson, _stjWithRefs);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Small Data Deserialization - No Refs
|
||||
|
||||
[Benchmark(Description = "AyCode NoRefs")]
|
||||
[BenchmarkCategory("Deserialize-Small-NoRefs")]
|
||||
public TestOrder? Deserialize_Small_AyCode_NoRefs()
|
||||
=> AcJsonDeserializer.Deserialize<TestOrder>(_smallAyCodeNoRefJson, _ayCodeNoRefs);
|
||||
|
||||
[Benchmark(Description = "STJ NoRefs", Baseline = true)]
|
||||
[BenchmarkCategory("Deserialize-Small-NoRefs")]
|
||||
public TestOrder? Deserialize_Small_STJ_NoRefs()
|
||||
=> JsonSerializer.Deserialize<TestOrder>(_smallStjNoRefJson, _stjNoRefs);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Medium Data Deserialization - With Refs
|
||||
|
||||
[Benchmark(Description = "AyCode WithRefs")]
|
||||
[BenchmarkCategory("Deserialize-Medium-WithRefs")]
|
||||
public TestOrder? Deserialize_Medium_AyCode_WithRefs()
|
||||
=> AcJsonDeserializer.Deserialize<TestOrder>(_mediumAyCodeJson, _ayCodeWithRefs);
|
||||
|
||||
[Benchmark(Description = "STJ WithRefs", Baseline = true)]
|
||||
[BenchmarkCategory("Deserialize-Medium-WithRefs")]
|
||||
public TestOrder? Deserialize_Medium_STJ_WithRefs()
|
||||
=> JsonSerializer.Deserialize<TestOrder>(_mediumStjJson, _stjWithRefs);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Medium Data Deserialization - No Refs
|
||||
|
||||
[Benchmark(Description = "AyCode NoRefs")]
|
||||
[BenchmarkCategory("Deserialize-Medium-NoRefs")]
|
||||
public TestOrder? Deserialize_Medium_AyCode_NoRefs()
|
||||
=> AcJsonDeserializer.Deserialize<TestOrder>(_mediumAyCodeNoRefJson, _ayCodeNoRefs);
|
||||
|
||||
[Benchmark(Description = "STJ NoRefs", Baseline = true)]
|
||||
[BenchmarkCategory("Deserialize-Medium-NoRefs")]
|
||||
public TestOrder? Deserialize_Medium_STJ_NoRefs()
|
||||
=> JsonSerializer.Deserialize<TestOrder>(_mediumStjNoRefJson, _stjNoRefs);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Large Data Deserialization - With Refs
|
||||
|
||||
[Benchmark(Description = "AyCode WithRefs")]
|
||||
[BenchmarkCategory("Deserialize-Large-WithRefs")]
|
||||
public TestOrder? Deserialize_Large_AyCode_WithRefs()
|
||||
=> AcJsonDeserializer.Deserialize<TestOrder>(_largeAyCodeJson, _ayCodeWithRefs);
|
||||
|
||||
[Benchmark(Description = "STJ WithRefs", Baseline = true)]
|
||||
[BenchmarkCategory("Deserialize-Large-WithRefs")]
|
||||
public TestOrder? Deserialize_Large_STJ_WithRefs()
|
||||
=> JsonSerializer.Deserialize<TestOrder>(_largeStjJson, _stjWithRefs);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Large Data Deserialization - No Refs
|
||||
|
||||
[Benchmark(Description = "AyCode NoRefs")]
|
||||
[BenchmarkCategory("Deserialize-Large-NoRefs")]
|
||||
public TestOrder? Deserialize_Large_AyCode_NoRefs()
|
||||
=> AcJsonDeserializer.Deserialize<TestOrder>(_largeAyCodeNoRefJson, _ayCodeNoRefs);
|
||||
|
||||
[Benchmark(Description = "STJ NoRefs", Baseline = true)]
|
||||
[BenchmarkCategory("Deserialize-Large-NoRefs")]
|
||||
public TestOrder? Deserialize_Large_STJ_NoRefs()
|
||||
=> JsonSerializer.Deserialize<TestOrder>(_largeStjNoRefJson, _stjNoRefs);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Populate Benchmarks - Small
|
||||
|
||||
[Benchmark(Description = "AyCode Populate")]
|
||||
[BenchmarkCategory("Populate-Small")]
|
||||
public void Populate_Small_AyCode()
|
||||
{
|
||||
var target = new TestOrder();
|
||||
AcJsonDeserializer.Populate(_smallAyCodeJson, target);
|
||||
}
|
||||
|
||||
[Benchmark(Description = "Newtonsoft PopulateObject", Baseline = true)]
|
||||
[BenchmarkCategory("Populate-Small")]
|
||||
public void Populate_Small_Newtonsoft()
|
||||
{
|
||||
var target = new TestOrder();
|
||||
JsonConvert.PopulateObject(_smallNewtonsoftJson, target, _newtonsoftSettings);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Populate Benchmarks - Medium
|
||||
|
||||
[Benchmark(Description = "AyCode Populate")]
|
||||
[BenchmarkCategory("Populate-Medium")]
|
||||
public void Populate_Medium_AyCode()
|
||||
{
|
||||
var target = new TestOrder();
|
||||
AcJsonDeserializer.Populate(_mediumAyCodeJson, target);
|
||||
}
|
||||
|
||||
[Benchmark(Description = "Newtonsoft PopulateObject", Baseline = true)]
|
||||
[BenchmarkCategory("Populate-Medium")]
|
||||
public void Populate_Medium_Newtonsoft()
|
||||
{
|
||||
var target = new TestOrder();
|
||||
JsonConvert.PopulateObject(_mediumNewtonsoftJson, target, _newtonsoftSettings);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Populate Benchmarks - Large
|
||||
|
||||
[Benchmark(Description = "AyCode Populate")]
|
||||
[BenchmarkCategory("Populate-Large")]
|
||||
public void Populate_Large_AyCode()
|
||||
{
|
||||
var target = new TestOrder();
|
||||
AcJsonDeserializer.Populate(_largeAyCodeJson, target);
|
||||
}
|
||||
|
||||
[Benchmark(Description = "Newtonsoft PopulateObject", Baseline = true)]
|
||||
[BenchmarkCategory("Populate-Large")]
|
||||
public void Populate_Large_Newtonsoft()
|
||||
{
|
||||
var target = new TestOrder();
|
||||
JsonConvert.PopulateObject(_largeNewtonsoftJson, target, _newtonsoftSettings);
|
||||
}
|
||||
|
||||
#endregion
|
||||
[Benchmark(Description = "MessagePack Deserialize")]
|
||||
public TestOrder? Deserialize_MsgPack() => MessagePackSerializer.Deserialize<TestOrder>(_msgPackData, _msgPackOptions);
|
||||
}
|
||||
Loading…
Reference in New Issue