AyCode.Core/AyCode.Core.Tests/Serialization/QuickBenchmark.cs

298 lines
12 KiB
C#

using System.Diagnostics;
using AyCode.Core.Extensions;
using AyCode.Core.Serializers.Binaries;
using AyCode.Core.Serializers.Jsons;
using AyCode.Core.Tests.TestModels;
using MessagePack;
using MessagePack.Resolvers;
namespace AyCode.Core.Tests.serialization;
[TestClass]
public class QuickBenchmark
{
private static readonly MessagePackSerializerOptions MsgPackOptions =
ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
[TestMethod]
public void RunQuickBenchmark()
{
// Warmup
var order = TestDataFactory.CreateBenchmarkOrder(2, 2, 2, 3);
for (int i = 0; i < 10; i++)
{
var bytes = order.ToBinary();
var result = bytes.BinaryTo<TestOrder>();
}
// Measure serialize
const int iterations = 1000;
var sw = Stopwatch.StartNew();
byte[] serialized = null!;
for (int i = 0; i < iterations; i++)
{
serialized = order.ToBinary();
}
sw.Stop();
var serializeMs = sw.Elapsed.TotalMilliseconds;
// Measure deserialize
sw.Restart();
TestOrder? deserialized = null;
for (int i = 0; i < iterations; i++)
{
deserialized = serialized.BinaryTo<TestOrder>();
}
sw.Stop();
var deserializeMs = sw.Elapsed.TotalMilliseconds;
// JSON comparison
var jsonOptions = AcJsonSerializerOptions.WithoutReferenceHandling();
sw.Restart();
string json = null!;
for (int i = 0; i < iterations; i++)
{
json = order.ToJson(jsonOptions);
}
sw.Stop();
var jsonSerializeMs = sw.Elapsed.TotalMilliseconds;
sw.Restart();
for (int i = 0; i < iterations; i++)
{
var _ = json.JsonTo<TestOrder>(jsonOptions);
}
sw.Stop();
var jsonDeserializeMs = sw.Elapsed.TotalMilliseconds;
Console.WriteLine($"=== Quick Benchmark ({iterations} iterations) ===");
Console.WriteLine($"Binary size: {serialized.Length} bytes");
Console.WriteLine($"JSON size: {json.Length} chars ({System.Text.Encoding.UTF8.GetByteCount(json)} bytes)");
Console.WriteLine();
Console.WriteLine($"Binary Serialize: {serializeMs:F2}ms ({serializeMs / iterations:F4}ms/op)");
Console.WriteLine($"Binary Deserialize: {deserializeMs:F2}ms ({deserializeMs / iterations:F4}ms/op)");
Console.WriteLine($"JSON Serialize: {jsonSerializeMs:F2}ms ({jsonSerializeMs / iterations:F4}ms/op)");
Console.WriteLine($"JSON Deserialize: {jsonDeserializeMs:F2}ms ({jsonDeserializeMs / iterations:F4}ms/op)");
Console.WriteLine();
Console.WriteLine($"Binary vs JSON Serialize: {serializeMs / jsonSerializeMs:F2}x");
Console.WriteLine($"Binary vs JSON Deserialize: {deserializeMs / jsonDeserializeMs:F2}x");
Console.WriteLine($"Size ratio: {100.0 * serialized.Length / System.Text.Encoding.UTF8.GetByteCount(json):F1}%");
Assert.IsNotNull(deserialized);
Assert.AreEqual(order.Id, deserialized.Id);
}
[TestMethod]
public void RunStringInterningBenchmark()
{
// Create data with repeated strings
var items = Enumerable.Range(0, 100).Select(i => new TestClassWithRepeatedValues
{
Id = i,
Status = i % 3 == 0 ? "Pending" : i % 3 == 1 ? "Processing" : "Completed",
Category = $"Category_{i % 5}",
Priority = i % 2 == 0 ? "High_Priority" : "Low_Priority"
}).ToList();
// Warmup
for (int i = 0; i < 10; i++)
{
var bytes = items.ToBinary();
var result = bytes.BinaryTo<List<TestClassWithRepeatedValues>>();
}
const int iterations = 1000;
// With interning (default)
var sw = Stopwatch.StartNew();
byte[] withInterning = null!;
for (int i = 0; i < iterations; i++)
{
withInterning = AcBinarySerializer.Serialize(items, AcBinarySerializerOptions.Default);
}
sw.Stop();
var withInterningMs = sw.Elapsed.TotalMilliseconds;
// Without interning
var noInternOptions = new AcBinarySerializerOptions { UseStringInterning = false };
sw.Restart();
byte[] withoutInterning = null!;
for (int i = 0; i < iterations; i++)
{
withoutInterning = AcBinarySerializer.Serialize(items, noInternOptions);
}
sw.Stop();
var withoutInterningMs = sw.Elapsed.TotalMilliseconds;
Console.WriteLine($"=== String Interning Benchmark ({iterations} iterations) ===");
Console.WriteLine($"With interning: {withInterning.Length} bytes, {withInterningMs:F2}ms");
Console.WriteLine($"Without interning: {withoutInterning.Length} bytes, {withoutInterningMs:F2}ms");
Console.WriteLine($"Size savings: {withoutInterning.Length - withInterning.Length} bytes ({100.0 * (withoutInterning.Length - withInterning.Length) / withoutInterning.Length:F1}%)");
Console.WriteLine($"Speed ratio: {withInterningMs / withoutInterningMs:F2}x");
// Verify both deserialize correctly
var result1 = withInterning.BinaryTo<List<TestClassWithRepeatedValues>>();
var result2 = withoutInterning.BinaryTo<List<TestClassWithRepeatedValues>>();
Assert.AreEqual(100, result1!.Count);
Assert.AreEqual(100, result2!.Count);
}
[TestMethod]
public void RunMessagePackComparison()
{
// Create test data
var order = TestDataFactory.CreateBenchmarkOrder(3, 3, 3, 4);
// Warmup
for (int i = 0; i < 20; i++)
{
var binBytes = AcBinarySerializer.Serialize(order, AcBinarySerializerOptions.Default);
var binResult = AcBinaryDeserializer.Deserialize<TestOrder>(binBytes);
var msgBytes = MessagePackSerializer.Serialize(order, MsgPackOptions);
var msgResult = MessagePackSerializer.Deserialize<TestOrder>(msgBytes, MsgPackOptions);
}
const int iterations = 1000;
// === AcBinary Serialize ===
var sw = Stopwatch.StartNew();
byte[] acBinaryData = null!;
for (int i = 0; i < iterations; i++)
{
acBinaryData = AcBinarySerializer.Serialize(order, AcBinarySerializerOptions.Default);
}
sw.Stop();
var acBinarySerMs = sw.Elapsed.TotalMilliseconds;
// === MessagePack Serialize ===
sw.Restart();
byte[] msgPackData = null!;
for (int i = 0; i < iterations; i++)
{
msgPackData = MessagePackSerializer.Serialize(order, MsgPackOptions);
}
sw.Stop();
var msgPackSerMs = sw.Elapsed.TotalMilliseconds;
// === AcBinary Deserialize ===
sw.Restart();
TestOrder? acBinaryResult = null;
for (int i = 0; i < iterations; i++)
{
acBinaryResult = AcBinaryDeserializer.Deserialize<TestOrder>(acBinaryData);
}
sw.Stop();
var acBinaryDeserMs = sw.Elapsed.TotalMilliseconds;
// === MessagePack Deserialize ===
sw.Restart();
TestOrder? msgPackResult = null;
for (int i = 0; i < iterations; i++)
{
msgPackResult = MessagePackSerializer.Deserialize<TestOrder>(msgPackData, MsgPackOptions);
}
sw.Stop();
var msgPackDeserMs = sw.Elapsed.TotalMilliseconds;
// Print results
Console.WriteLine($"=== AcBinary vs MessagePack Benchmark ({iterations} iterations) ===");
Console.WriteLine();
Console.WriteLine($"{"Metric",-25} {"AcBinary",12} {"MessagePack",12} {"Ratio",10}");
Console.WriteLine(new string('-', 60));
Console.WriteLine($"{"Size (bytes)",-25} {acBinaryData.Length,12:N0} {msgPackData.Length,12:N0} {100.0 * acBinaryData.Length / msgPackData.Length,9:F1}%");
Console.WriteLine($"{"Serialize (ms)",-25} {acBinarySerMs,12:F2} {msgPackSerMs,12:F2} {acBinarySerMs / msgPackSerMs,9:F2}x");
Console.WriteLine($"{"Deserialize (ms)",-25} {acBinaryDeserMs,12:F2} {msgPackDeserMs,12:F2} {acBinaryDeserMs / msgPackDeserMs,9:F2}x");
Console.WriteLine($"{"Round-trip (ms)",-25} {acBinarySerMs + acBinaryDeserMs,12:F2} {msgPackSerMs + msgPackDeserMs,12:F2} {(acBinarySerMs + acBinaryDeserMs) / (msgPackSerMs + msgPackDeserMs),9:F2}x");
Console.WriteLine();
var sizeDiff = msgPackData.Length - acBinaryData.Length;
if (sizeDiff > 0)
Console.WriteLine($"✅ AcBinary {sizeDiff:N0} bytes smaller ({100.0 * sizeDiff / msgPackData.Length:F1}% savings)");
else
Console.WriteLine($"⚠️ AcBinary {-sizeDiff:N0} bytes larger");
Assert.IsNotNull(acBinaryResult);
Assert.IsNotNull(msgPackResult);
Assert.AreEqual(order.Id, acBinaryResult.Id);
}
[TestMethod]
public void RunStringInterningVsMessagePack()
{
// Create data with many repeated strings (worst case for MessagePack, best for interning)
var items = Enumerable.Range(0, 200).Select(i => new TestClassWithRepeatedValues
{
Id = i,
Status = i % 3 == 0 ? "PendingStatus" : i % 3 == 1 ? "ProcessingStatus" : "CompletedStatus",
Category = $"Category_{i % 5}",
Priority = i % 2 == 0 ? "HighPriority" : "LowPriority"
}).ToList();
// Warmup
for (int i = 0; i < 20; i++)
{
var b1 = AcBinarySerializer.Serialize(items, AcBinarySerializerOptions.Default);
var r1 = AcBinaryDeserializer.Deserialize<List<TestClassWithRepeatedValues>>(b1);
var b2 = MessagePackSerializer.Serialize(items, MsgPackOptions);
var r2 = MessagePackSerializer.Deserialize<List<TestClassWithRepeatedValues>>(b2, MsgPackOptions);
}
const int iterations = 1000;
// AcBinary with interning
var sw = Stopwatch.StartNew();
byte[] acWithIntern = null!;
for (int i = 0; i < iterations; i++)
{
acWithIntern = AcBinarySerializer.Serialize(items, AcBinarySerializerOptions.Default);
}
var acSerMs = sw.Elapsed.TotalMilliseconds;
sw.Restart();
for (int i = 0; i < iterations; i++)
{
var _ = AcBinaryDeserializer.Deserialize<List<TestClassWithRepeatedValues>>(acWithIntern);
}
var acDeserMs = sw.Elapsed.TotalMilliseconds;
// MessagePack
sw.Restart();
byte[] msgPack = null!;
for (int i = 0; i < iterations; i++)
{
msgPack = MessagePackSerializer.Serialize(items, MsgPackOptions);
}
var msgSerMs = sw.Elapsed.TotalMilliseconds;
sw.Restart();
for (int i = 0; i < iterations; i++)
{
var _ = MessagePackSerializer.Deserialize<List<TestClassWithRepeatedValues>>(msgPack, MsgPackOptions);
}
var msgDeserMs = sw.Elapsed.TotalMilliseconds;
Console.WriteLine($"=== String Interning Advantage ({iterations} iterations, 200 items with repeated strings) ===");
Console.WriteLine();
Console.WriteLine($"{"Metric",-25} {"AcBinary",12} {"MessagePack",12} {"Ratio",10}");
Console.WriteLine(new string('-', 60));
Console.WriteLine($"{"Size (bytes)",-25} {acWithIntern.Length,12:N0} {msgPack.Length,12:N0} {100.0 * acWithIntern.Length / msgPack.Length,9:F1}%");
Console.WriteLine($"{"Serialize (ms)",-25} {acSerMs,12:F2} {msgSerMs,12:F2} {acSerMs / msgSerMs,9:F2}x");
Console.WriteLine($"{"Deserialize (ms)",-25} {acDeserMs,12:F2} {msgDeserMs,12:F2} {acDeserMs / msgDeserMs,9:F2}x");
Console.WriteLine();
var sizeSaving = msgPack.Length - acWithIntern.Length;
Console.WriteLine($"✅ String interning saves {sizeSaving:N0} bytes ({100.0 * sizeSaving / msgPack.Length:F1}%)");
Assert.IsTrue(acWithIntern.Length < msgPack.Length, "AcBinary with interning should be smaller");
}
// Public for MessagePack dynamic serializer compatibility
public class TestClassWithRepeatedValues
{
public int Id { get; set; }
public string Status { get; set; } = "";
public string Category { get; set; } = "";
public string Priority { get; set; } = "";
}
}