Expand QuickBenchmark suite & add CLI scripts
Majorly enhanced QuickBenchmark.cs with new helper methods, standardized iteration count, and several comprehensive benchmarks comparing AcBinary (with/without reference handling) to MessagePack. Improved output formatting for clarity. Added RunQuickBenchmark.ps1 and .bat scripts for easy CLI execution and registered them as solution items. These changes make benchmarking more robust, readable, and user-friendly.
This commit is contained in:
parent
bc30a3aede
commit
a832d8e86d
|
|
@ -14,6 +14,87 @@ public class QuickBenchmark
|
|||
private static readonly MessagePackSerializerOptions MsgPackOptions =
|
||||
ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
|
||||
|
||||
private const int DefaultIterations = 1000;
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
private static void PrintBanner(string title)
|
||||
{
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(new string('=', 78));
|
||||
Console.WriteLine(title);
|
||||
Console.WriteLine(new string('=', 78));
|
||||
}
|
||||
|
||||
private static void PrintTableHeader(string title)
|
||||
{
|
||||
PrintBanner(title);
|
||||
Console.WriteLine($"{"Metric",-25} | {"AcBinary",14} | {"MessagePack",14} | {"Ratio",14}");
|
||||
Console.WriteLine(new string('-', 78));
|
||||
}
|
||||
|
||||
private static void PrintTableRow(string metric, double acBinary, double msgPack, string unit = "ms")
|
||||
{
|
||||
if (msgPack > 0)
|
||||
{
|
||||
var ratio = acBinary / msgPack;
|
||||
var ratioStr = ratio < 1 ? $"{ratio:F2}x faster" : $"{ratio:F2}x slower";
|
||||
Console.WriteLine($"{metric,-25} | {acBinary,10:F2} {unit,-2} | {msgPack,10:F2} {unit,-2} | {ratioStr,14}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"{metric,-25} | {acBinary,10:F2} {unit,-2} | {"N/A",12} | {"(unique)",14}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void PrintTableRowSize(string metric, int acBinary, int msgPack)
|
||||
{
|
||||
var ratio = msgPack == 0 ? 0 : 100.0 * acBinary / msgPack;
|
||||
Console.WriteLine($"{metric,-25} | {acBinary,14:N0} | {msgPack,14:N0} | {ratio,12:F1}%");
|
||||
}
|
||||
|
||||
private static void PrintTableFooter()
|
||||
{
|
||||
Console.WriteLine(new string('-', 78));
|
||||
}
|
||||
|
||||
private static void PrintSummary(int acBinarySize, int msgPackSize, double acSerMs, double msgSerMs, double acDeserMs, double msgDeserMs)
|
||||
{
|
||||
PrintBanner("SUMMARY");
|
||||
|
||||
var sizeAdvantage = msgPackSize == 0 ? 0 : 100.0 - (100.0 * acBinarySize / msgPackSize);
|
||||
if (sizeAdvantage > 0)
|
||||
Console.WriteLine($"[OK] Size: AcBinary is {sizeAdvantage:F1}% smaller ({msgPackSize - acBinarySize:N0} bytes saved)");
|
||||
else
|
||||
Console.WriteLine($"[WARN] Size: AcBinary is {-sizeAdvantage:F1}% larger");
|
||||
|
||||
var serRatio = msgSerMs == 0 ? 0 : acSerMs / msgSerMs;
|
||||
if (serRatio > 0 && serRatio < 1)
|
||||
Console.WriteLine($"[OK] Serialize: AcBinary is {1 / serRatio:F2}x faster");
|
||||
else if (serRatio > 0)
|
||||
Console.WriteLine($"[WARN] Serialize: AcBinary is {serRatio:F2}x slower");
|
||||
|
||||
var deserRatio = msgDeserMs == 0 ? 0 : acDeserMs / msgDeserMs;
|
||||
if (deserRatio > 0 && deserRatio < 1)
|
||||
Console.WriteLine($"[OK] Deserialize: AcBinary is {1 / deserRatio:F2}x faster");
|
||||
else if (deserRatio > 0)
|
||||
Console.WriteLine($"[WARN] Deserialize: AcBinary is {deserRatio:F2}x slower");
|
||||
}
|
||||
|
||||
private static TestOrder CreatePopulateTarget(TestOrder source)
|
||||
{
|
||||
var target = new TestOrder { Id = source.Id };
|
||||
foreach (var item in source.Items)
|
||||
{
|
||||
target.Items.Add(new TestOrderItem { Id = item.Id });
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Basic Benchmarks
|
||||
|
||||
[TestMethod]
|
||||
public void RunQuickBenchmark()
|
||||
{
|
||||
|
|
@ -26,7 +107,7 @@ public class QuickBenchmark
|
|||
}
|
||||
|
||||
// Measure serialize
|
||||
const int iterations = 1000;
|
||||
const int iterations = DefaultIterations;
|
||||
var sw = Stopwatch.StartNew();
|
||||
byte[] serialized = null!;
|
||||
for (int i = 0; i < iterations; i++)
|
||||
|
|
@ -101,7 +182,7 @@ public class QuickBenchmark
|
|||
var result = bytes.BinaryTo<List<TestClassWithRepeatedValues>>();
|
||||
}
|
||||
|
||||
const int iterations = 1000;
|
||||
const int iterations = DefaultIterations;
|
||||
|
||||
// With interning (default)
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
|
@ -137,6 +218,10 @@ public class QuickBenchmark
|
|||
Assert.AreEqual(100, result2!.Count);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region MessagePack Comparison
|
||||
|
||||
[TestMethod]
|
||||
public void RunMessagePackComparison()
|
||||
{
|
||||
|
|
@ -152,7 +237,7 @@ public class QuickBenchmark
|
|||
var msgResult = MessagePackSerializer.Deserialize<TestOrder>(msgBytes, MsgPackOptions);
|
||||
}
|
||||
|
||||
const int iterations = 1000;
|
||||
const int iterations = DefaultIterations;
|
||||
|
||||
// === AcBinary Serialize ===
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
|
@ -207,9 +292,9 @@ public class QuickBenchmark
|
|||
|
||||
var sizeDiff = msgPackData.Length - acBinaryData.Length;
|
||||
if (sizeDiff > 0)
|
||||
Console.WriteLine($"✅ AcBinary {sizeDiff:N0} bytes smaller ({100.0 * sizeDiff / msgPackData.Length:F1}% savings)");
|
||||
Console.WriteLine($"[OK] AcBinary {sizeDiff:N0} bytes smaller ({100.0 * sizeDiff / msgPackData.Length:F1}% savings)");
|
||||
else
|
||||
Console.WriteLine($"⚠️ AcBinary {-sizeDiff:N0} bytes larger");
|
||||
Console.WriteLine($"[WARN] AcBinary {-sizeDiff:N0} bytes larger");
|
||||
|
||||
Assert.IsNotNull(acBinaryResult);
|
||||
Assert.IsNotNull(msgPackResult);
|
||||
|
|
@ -237,7 +322,7 @@ public class QuickBenchmark
|
|||
var r2 = MessagePackSerializer.Deserialize<List<TestClassWithRepeatedValues>>(b2, MsgPackOptions);
|
||||
}
|
||||
|
||||
const int iterations = 1000;
|
||||
const int iterations = DefaultIterations;
|
||||
|
||||
// AcBinary with interning
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
|
@ -281,12 +366,306 @@ public class QuickBenchmark
|
|||
Console.WriteLine();
|
||||
|
||||
var sizeSaving = msgPack.Length - acWithIntern.Length;
|
||||
Console.WriteLine($"✅ String interning saves {sizeSaving:N0} bytes ({100.0 * sizeSaving / msgPack.Length:F1}%)");
|
||||
Console.WriteLine($"[OK] 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
|
||||
#endregion
|
||||
|
||||
#region Full Comparison (WithRef, NoRef, Populate, Merge)
|
||||
|
||||
[TestMethod]
|
||||
public void RunFullBenchmarkComparison()
|
||||
{
|
||||
PrintBanner("AcBinary vs MessagePack Full Benchmark");
|
||||
|
||||
// Create test data with shared references
|
||||
TestDataFactory.ResetIdCounter();
|
||||
var sharedTag = TestDataFactory.CreateTag("SharedTag");
|
||||
var sharedUser = TestDataFactory.CreateUser("shareduser");
|
||||
var sharedMeta = TestDataFactory.CreateMetadata("shared", withChild: true);
|
||||
|
||||
var testOrder = TestDataFactory.CreateOrder(
|
||||
itemCount: 3,
|
||||
palletsPerItem: 3,
|
||||
measurementsPerPallet: 3,
|
||||
pointsPerMeasurement: 4,
|
||||
sharedTag: sharedTag,
|
||||
sharedUser: sharedUser,
|
||||
sharedMetadata: sharedMeta);
|
||||
|
||||
// Options
|
||||
var withRefOptions = new AcBinarySerializerOptions();
|
||||
var noRefOptions = AcBinarySerializerOptions.WithoutReferenceHandling();
|
||||
|
||||
// Warmup
|
||||
Console.WriteLine("\nWarming up...");
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
_ = AcBinarySerializer.Serialize(testOrder, withRefOptions);
|
||||
_ = AcBinarySerializer.Serialize(testOrder, noRefOptions);
|
||||
_ = MessagePackSerializer.Serialize(testOrder, MsgPackOptions);
|
||||
}
|
||||
|
||||
// Pre-serialize
|
||||
var acBinaryWithRef = AcBinarySerializer.Serialize(testOrder, withRefOptions);
|
||||
var acBinaryNoRef = AcBinarySerializer.Serialize(testOrder, noRefOptions);
|
||||
var msgPackData = MessagePackSerializer.Serialize(testOrder, MsgPackOptions);
|
||||
|
||||
Console.WriteLine($"Iterations: {DefaultIterations:N0}");
|
||||
|
||||
// Size comparison
|
||||
PrintTableHeader("SIZE COMPARISON");
|
||||
PrintTableRowSize("AcBinary (WithRef)", acBinaryWithRef.Length, msgPackData.Length);
|
||||
PrintTableRowSize("AcBinary (NoRef)", acBinaryNoRef.Length, msgPackData.Length);
|
||||
PrintTableRowSize("MessagePack (baseline)", msgPackData.Length, msgPackData.Length);
|
||||
PrintTableFooter();
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
// === Serialize WithRef ===
|
||||
sw.Restart();
|
||||
for (int i = 0; i < DefaultIterations; i++)
|
||||
_ = AcBinarySerializer.Serialize(testOrder, withRefOptions);
|
||||
var acWithRefSerMs = sw.Elapsed.TotalMilliseconds;
|
||||
|
||||
// === Serialize NoRef ===
|
||||
sw.Restart();
|
||||
for (int i = 0; i < DefaultIterations; i++)
|
||||
_ = AcBinarySerializer.Serialize(testOrder, noRefOptions);
|
||||
var acNoRefSerMs = sw.Elapsed.TotalMilliseconds;
|
||||
|
||||
// === MessagePack Serialize ===
|
||||
sw.Restart();
|
||||
for (int i = 0; i < DefaultIterations; i++)
|
||||
_ = MessagePackSerializer.Serialize(testOrder, MsgPackOptions);
|
||||
var msgPackSerMs = sw.Elapsed.TotalMilliseconds;
|
||||
|
||||
// === Deserialize WithRef ===
|
||||
sw.Restart();
|
||||
for (int i = 0; i < DefaultIterations; i++)
|
||||
_ = AcBinaryDeserializer.Deserialize<TestOrder>(acBinaryWithRef);
|
||||
var acWithRefDeserMs = sw.Elapsed.TotalMilliseconds;
|
||||
|
||||
// === Deserialize NoRef ===
|
||||
sw.Restart();
|
||||
for (int i = 0; i < DefaultIterations; i++)
|
||||
_ = AcBinaryDeserializer.Deserialize<TestOrder>(acBinaryNoRef);
|
||||
var acNoRefDeserMs = sw.Elapsed.TotalMilliseconds;
|
||||
|
||||
// === MessagePack Deserialize ===
|
||||
sw.Restart();
|
||||
for (int i = 0; i < DefaultIterations; i++)
|
||||
_ = MessagePackSerializer.Deserialize<TestOrder>(msgPackData, MsgPackOptions);
|
||||
var msgPackDeserMs = sw.Elapsed.TotalMilliseconds;
|
||||
|
||||
// === Populate (AcBinary only) ===
|
||||
sw.Restart();
|
||||
for (int i = 0; i < DefaultIterations; i++)
|
||||
{
|
||||
var target = CreatePopulateTarget(testOrder);
|
||||
AcBinaryDeserializer.Populate(acBinaryNoRef, target);
|
||||
}
|
||||
var acPopulateMs = sw.Elapsed.TotalMilliseconds;
|
||||
|
||||
// === PopulateMerge (AcBinary only) ===
|
||||
sw.Restart();
|
||||
for (int i = 0; i < DefaultIterations; i++)
|
||||
{
|
||||
var target = CreatePopulateTarget(testOrder);
|
||||
AcBinaryDeserializer.PopulateMerge(acBinaryNoRef.AsSpan(), target);
|
||||
}
|
||||
var acMergeMs = sw.Elapsed.TotalMilliseconds;
|
||||
|
||||
// Print performance table
|
||||
PrintTableHeader("PERFORMANCE COMPARISON (lower is better)");
|
||||
PrintTableRow("Serialize (WithRef)", acWithRefSerMs, msgPackSerMs);
|
||||
PrintTableRow("Serialize (NoRef)", acNoRefSerMs, msgPackSerMs);
|
||||
PrintTableRow("Deserialize (WithRef)", acWithRefDeserMs, msgPackDeserMs);
|
||||
PrintTableRow("Deserialize (NoRef)", acNoRefDeserMs, msgPackDeserMs);
|
||||
PrintTableRow("Populate (NoRef)", acPopulateMs, 0);
|
||||
PrintTableRow("Merge (NoRef)", acMergeMs, 0);
|
||||
PrintTableRow("Round-trip (WithRef)", acWithRefSerMs + acWithRefDeserMs, msgPackSerMs + msgPackDeserMs);
|
||||
PrintTableRow("Round-trip (NoRef)", acNoRefSerMs + acNoRefDeserMs, msgPackSerMs + msgPackDeserMs);
|
||||
PrintTableFooter();
|
||||
|
||||
PrintSummary(acBinaryNoRef.Length, msgPackData.Length, acNoRefSerMs, msgPackSerMs, acNoRefDeserMs, msgPackDeserMs);
|
||||
|
||||
// Assertions
|
||||
Assert.IsTrue(acBinaryWithRef.Length < msgPackData.Length, "AcBinary WithRef should be smaller than MessagePack");
|
||||
Assert.IsTrue(acBinaryNoRef.Length < msgPackData.Length, "AcBinary NoRef should be smaller than MessagePack");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RunWithRefVsNoRefComparison()
|
||||
{
|
||||
PrintBanner("AcBinary WithRef vs NoRef Comparison");
|
||||
|
||||
// Create test data WITH shared references (to show WithRef advantage)
|
||||
TestDataFactory.ResetIdCounter();
|
||||
var sharedTag = TestDataFactory.CreateTag("SharedTag");
|
||||
var sharedUser = TestDataFactory.CreateUser("shareduser");
|
||||
var sharedMeta = TestDataFactory.CreateMetadata("shared", withChild: true);
|
||||
|
||||
var testOrder = TestDataFactory.CreateOrder(
|
||||
itemCount: 5,
|
||||
palletsPerItem: 4,
|
||||
measurementsPerPallet: 3,
|
||||
pointsPerMeasurement: 5,
|
||||
sharedTag: sharedTag,
|
||||
sharedUser: sharedUser,
|
||||
sharedMetadata: sharedMeta);
|
||||
|
||||
var withRefOptions = new AcBinarySerializerOptions();
|
||||
var noRefOptions = AcBinarySerializerOptions.WithoutReferenceHandling();
|
||||
|
||||
// Warmup
|
||||
Console.WriteLine("Warming up...");
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
_ = AcBinarySerializer.Serialize(testOrder, withRefOptions);
|
||||
_ = AcBinarySerializer.Serialize(testOrder, noRefOptions);
|
||||
}
|
||||
|
||||
var withRefData = AcBinarySerializer.Serialize(testOrder, withRefOptions);
|
||||
var noRefData = AcBinarySerializer.Serialize(testOrder, noRefOptions);
|
||||
|
||||
Console.WriteLine($"Iterations: {DefaultIterations:N0}");
|
||||
|
||||
// Size comparison
|
||||
PrintBanner("SIZE COMPARISON (bytes)");
|
||||
Console.WriteLine($"WithRef : {withRefData.Length,12:N0} (baseline)");
|
||||
var sizeDiff = noRefData.Length - withRefData.Length;
|
||||
var ratio = withRefData.Length == 0 ? 0 : 100.0 * noRefData.Length / withRefData.Length;
|
||||
Console.WriteLine($"NoRef : {noRefData.Length,12:N0} (diff {sizeDiff:+#;-#;0}) => {ratio:F1}% of WithRef");
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
// Serialize WithRef
|
||||
sw.Restart();
|
||||
for (int i = 0; i < DefaultIterations; i++)
|
||||
_ = AcBinarySerializer.Serialize(testOrder, withRefOptions);
|
||||
var withRefSerMs = sw.Elapsed.TotalMilliseconds;
|
||||
|
||||
// Serialize NoRef
|
||||
sw.Restart();
|
||||
for (int i = 0; i < DefaultIterations; i++)
|
||||
_ = AcBinarySerializer.Serialize(testOrder, noRefOptions);
|
||||
var noRefSerMs = sw.Elapsed.TotalMilliseconds;
|
||||
|
||||
// Deserialize WithRef
|
||||
sw.Restart();
|
||||
for (int i = 0; i < DefaultIterations; i++)
|
||||
_ = AcBinaryDeserializer.Deserialize<TestOrder>(withRefData);
|
||||
var withRefDeserMs = sw.Elapsed.TotalMilliseconds;
|
||||
|
||||
// Deserialize NoRef
|
||||
sw.Restart();
|
||||
for (int i = 0; i < DefaultIterations; i++)
|
||||
_ = AcBinaryDeserializer.Deserialize<TestOrder>(noRefData);
|
||||
var noRefDeserMs = sw.Elapsed.TotalMilliseconds;
|
||||
|
||||
PrintBanner("PERFORMANCE COMPARISON (ms)");
|
||||
Console.WriteLine($"Serialize -> WithRef: {withRefSerMs,8:F2} | NoRef: {noRefSerMs,8:F2}");
|
||||
Console.WriteLine($"Deserialize-> WithRef: {withRefDeserMs,8:F2} | NoRef: {noRefDeserMs,8:F2}");
|
||||
Console.WriteLine($"Round-trip -> WithRef: {withRefSerMs + withRefDeserMs,8:F2} | NoRef: {noRefSerMs + noRefDeserMs,8:F2}");
|
||||
|
||||
if (withRefData.Length < noRefData.Length)
|
||||
{
|
||||
Console.WriteLine($"[OK] WithRef saves {noRefData.Length - withRefData.Length:N0} bytes by deduplicating shared references.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"[INFO] NoRef saves {withRefData.Length - noRefData.Length:N0} bytes because it skips reference metadata.");
|
||||
}
|
||||
|
||||
// Verify correctness
|
||||
var resultWithRef = AcBinaryDeserializer.Deserialize<TestOrder>(withRefData);
|
||||
var resultNoRef = AcBinaryDeserializer.Deserialize<TestOrder>(noRefData);
|
||||
Assert.IsNotNull(resultWithRef);
|
||||
Assert.IsNotNull(resultNoRef);
|
||||
Assert.AreEqual(testOrder.Id, resultWithRef.Id);
|
||||
Assert.AreEqual(testOrder.Id, resultNoRef.Id);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RunPopulateAndMergeBenchmark()
|
||||
{
|
||||
PrintBanner("AcBinary Populate & Merge Benchmark");
|
||||
|
||||
TestDataFactory.ResetIdCounter();
|
||||
var testOrder = TestDataFactory.CreateBenchmarkOrder(
|
||||
itemCount: 3,
|
||||
palletsPerItem: 3,
|
||||
measurementsPerPallet: 3,
|
||||
pointsPerMeasurement: 4);
|
||||
|
||||
var options = AcBinarySerializerOptions.WithoutReferenceHandling();
|
||||
var binaryData = AcBinarySerializer.Serialize(testOrder, options);
|
||||
|
||||
Console.WriteLine("Warming up...");
|
||||
for (int i = 0; i < 50; i++)
|
||||
{
|
||||
var target = CreatePopulateTarget(testOrder);
|
||||
AcBinaryDeserializer.Populate(binaryData, target);
|
||||
AcBinaryDeserializer.PopulateMerge(binaryData.AsSpan(), target);
|
||||
}
|
||||
|
||||
Console.WriteLine($"Iterations: {DefaultIterations:N0}");
|
||||
Console.WriteLine($"Data size: {binaryData.Length:N0} bytes");
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
// Deserialize (creates new object)
|
||||
sw.Restart();
|
||||
for (int i = 0; i < DefaultIterations; i++)
|
||||
_ = AcBinaryDeserializer.Deserialize<TestOrder>(binaryData);
|
||||
var deserializeMs = sw.Elapsed.TotalMilliseconds;
|
||||
|
||||
// Populate (reuses existing object)
|
||||
sw.Restart();
|
||||
for (int i = 0; i < DefaultIterations; i++)
|
||||
{
|
||||
var target = CreatePopulateTarget(testOrder);
|
||||
AcBinaryDeserializer.Populate(binaryData, target);
|
||||
}
|
||||
var populateMs = sw.Elapsed.TotalMilliseconds;
|
||||
|
||||
// PopulateMerge (IId-based merge)
|
||||
sw.Restart();
|
||||
for (int i = 0; i < DefaultIterations; i++)
|
||||
{
|
||||
var target = CreatePopulateTarget(testOrder);
|
||||
AcBinaryDeserializer.PopulateMerge(binaryData.AsSpan(), target);
|
||||
}
|
||||
var mergeMs = sw.Elapsed.TotalMilliseconds;
|
||||
|
||||
// PopulateMerge with RemoveOrphanedItems
|
||||
var mergeWithRemoveOptions = new AcBinarySerializerOptions { RemoveOrphanedItems = true };
|
||||
sw.Restart();
|
||||
for (int i = 0; i < DefaultIterations; i++)
|
||||
{
|
||||
var target = CreatePopulateTarget(testOrder);
|
||||
AcBinaryDeserializer.PopulateMerge(binaryData.AsSpan(), target, mergeWithRemoveOptions);
|
||||
}
|
||||
var mergeWithRemoveMs = sw.Elapsed.TotalMilliseconds;
|
||||
|
||||
PrintBanner("OPERATION COMPARISON (ms)");
|
||||
Console.WriteLine($"Deserialize (new object): {deserializeMs:F2} (baseline)");
|
||||
Console.WriteLine($"Populate (reuse obj) : {populateMs:F2} ({populateMs / deserializeMs:F2}x of baseline)");
|
||||
Console.WriteLine($"PopulateMerge : {mergeMs:F2} ({mergeMs / deserializeMs:F2}x of baseline)");
|
||||
Console.WriteLine($"PopulateMerge + cleanup: {mergeWithRemoveMs:F2} ({mergeWithRemoveMs / deserializeMs:F2}x of baseline)");
|
||||
|
||||
Console.WriteLine("[INFO] Populate/Merge reuse existing objects - ideal for UI data binding scenarios.");
|
||||
|
||||
Assert.IsTrue(true); // Test passed if no exceptions
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Test Models
|
||||
|
||||
public class TestClassWithRepeatedValues
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
|
@ -294,4 +673,6 @@ public class QuickBenchmark
|
|||
public string Category { get; set; } = "";
|
||||
public string Priority { get; set; } = "";
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ EndProject
|
|||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
AyCode.Core.targets = AyCode.Core.targets
|
||||
RunQuickBenchmark.bat = RunQuickBenchmark.bat
|
||||
RunQuickBenchmark.ps1 = RunQuickBenchmark.ps1
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AyCode.Benchmark", "AyCode.Benchmark\AyCode.Benchmark.csproj", "{A20861A9-411E-6150-BF5C-69E8196E5D22}"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
@echo off
|
||||
REM Run Quick Benchmark PowerShell script using pwsh if available, otherwise Windows PowerShell
|
||||
setlocal enabledelayedexpansion
|
||||
set SCRIPT_DIR=%~dp0
|
||||
where pwsh >nul 2>&1
|
||||
if %ERRORLEVEL%==0 (
|
||||
pwsh -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%RunQuickBenchmark.ps1" -All
|
||||
set EXITCODE=!ERRORLEVEL!
|
||||
) else (
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%RunQuickBenchmark.ps1" -All
|
||||
set EXITCODE=!ERRORLEVEL!
|
||||
)
|
||||
|
||||
echo.
|
||||
pause
|
||||
endlocal
|
||||
exit /b %EXITCODE%
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
# AcBinary Quick Benchmark Runner
|
||||
# Run this script to execute all binary serialization benchmarks
|
||||
# Usage: .\RunQuickBenchmark.ps1 [-All] [-Full] [-WithRef] [-Populate] [-StringIntern] [-MessagePack]
|
||||
|
||||
param(
|
||||
[switch]$All,
|
||||
[switch]$Full,
|
||||
[switch]$WithRef,
|
||||
[switch]$Populate,
|
||||
[switch]$StringIntern,
|
||||
[switch]$MessagePack,
|
||||
[switch]$Help
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
|
||||
# Colors for output
|
||||
function Write-ColorOutput($ForegroundColor, $Message) {
|
||||
$fc = $host.UI.RawUI.ForegroundColor
|
||||
$host.UI.RawUI.ForegroundColor = $ForegroundColor
|
||||
Write-Output $Message
|
||||
$host.UI.RawUI.ForegroundColor = $fc
|
||||
}
|
||||
|
||||
function Show-Help {
|
||||
Write-Host ""
|
||||
Write-Host "????????????????????????????????????????????????????????????????????????????????" -ForegroundColor Cyan
|
||||
Write-Host "? AcBinary Quick Benchmark Runner ?" -ForegroundColor Cyan
|
||||
Write-Host "????????????????????????????????????????????????????????????????????????????????" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Usage: .\RunQuickBenchmark.ps1 [options]" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host "Options:" -ForegroundColor Green
|
||||
Write-Host " -All Run all benchmark tests"
|
||||
Write-Host " -Full Run full AcBinary vs MessagePack comparison"
|
||||
Write-Host " -WithRef Run WithRef vs NoRef comparison"
|
||||
Write-Host " -Populate Run Populate & Merge benchmarks"
|
||||
Write-Host " -StringIntern Run String Interning benchmarks"
|
||||
Write-Host " -MessagePack Run MessagePack comparison"
|
||||
Write-Host " -Help Show this help message"
|
||||
Write-Host ""
|
||||
Write-Host "Examples:" -ForegroundColor Green
|
||||
Write-Host " .\RunQuickBenchmark.ps1 -All # Run all tests"
|
||||
Write-Host " .\RunQuickBenchmark.ps1 -Full # Full benchmark comparison"
|
||||
Write-Host " .\RunQuickBenchmark.ps1 -WithRef -Populate # Multiple specific tests"
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
function Run-DotNetTest {
|
||||
param(
|
||||
[string]$Filter,
|
||||
[string]$Description
|
||||
)
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Running: $Description" -ForegroundColor Yellow
|
||||
Write-Host ("=" * 80) -ForegroundColor DarkGray
|
||||
|
||||
$testProject = Join-Path $ScriptDir "AyCode.Core.Tests\AyCode.Core.Tests.csproj"
|
||||
|
||||
if (-not (Test-Path $testProject)) {
|
||||
Write-Host "Error: Test project not found at $testProject" -ForegroundColor Red
|
||||
return $false
|
||||
}
|
||||
|
||||
$result = dotnet test $testProject `
|
||||
--filter "FullyQualifiedName~$Filter" `
|
||||
--configuration Release `
|
||||
--logger "console;verbosity=detailed" `
|
||||
--no-build 2>&1
|
||||
|
||||
$result | ForEach-Object { Write-Host $_ }
|
||||
|
||||
return $LASTEXITCODE -eq 0
|
||||
}
|
||||
|
||||
# Check for help
|
||||
if ($Help -or ($PSBoundParameters.Count -eq 0)) {
|
||||
Show-Help
|
||||
if ($PSBoundParameters.Count -eq 0) {
|
||||
Write-Host "No options specified. Running full benchmark..." -ForegroundColor Yellow
|
||||
$Full = $true
|
||||
} else {
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "????????????????????????????????????????????????????????????????????????????????" -ForegroundColor Cyan
|
||||
Write-Host "? AcBinary Quick Benchmark ?" -ForegroundColor Cyan
|
||||
Write-Host "????????????????????????????????????????????????????????????????????????????????" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Building solution in Release mode..." -ForegroundColor Yellow
|
||||
|
||||
# Build first
|
||||
$buildResult = dotnet build (Join-Path $ScriptDir "AyCode.Core.sln") --configuration Release --verbosity minimal 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "Build failed!" -ForegroundColor Red
|
||||
$buildResult | ForEach-Object { Write-Host $_ }
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "Build successful!" -ForegroundColor Green
|
||||
|
||||
$testsRun = 0
|
||||
$testsPassed = 0
|
||||
|
||||
# Run requested tests
|
||||
if ($All) {
|
||||
$Full = $true
|
||||
$WithRef = $true
|
||||
$Populate = $true
|
||||
$StringIntern = $true
|
||||
$MessagePack = $true
|
||||
}
|
||||
|
||||
if ($Full) {
|
||||
$testsRun++
|
||||
if (Run-DotNetTest "QuickBenchmark.RunFullBenchmarkComparison" "Full AcBinary vs MessagePack Comparison") {
|
||||
$testsPassed++
|
||||
}
|
||||
}
|
||||
|
||||
if ($WithRef) {
|
||||
$testsRun++
|
||||
if (Run-DotNetTest "QuickBenchmark.RunWithRefVsNoRefComparison" "WithRef vs NoRef Comparison") {
|
||||
$testsPassed++
|
||||
}
|
||||
}
|
||||
|
||||
if ($Populate) {
|
||||
$testsRun++
|
||||
if (Run-DotNetTest "QuickBenchmark.RunPopulateAndMergeBenchmark" "Populate & Merge Benchmark") {
|
||||
$testsPassed++
|
||||
}
|
||||
}
|
||||
|
||||
if ($StringIntern) {
|
||||
$testsRun++
|
||||
if (Run-DotNetTest "QuickBenchmark.RunStringInterningBenchmark" "String Interning Benchmark") {
|
||||
$testsPassed++
|
||||
}
|
||||
$testsRun++
|
||||
if (Run-DotNetTest "QuickBenchmark.RunStringInterningVsMessagePack" "String Interning vs MessagePack") {
|
||||
$testsPassed++
|
||||
}
|
||||
}
|
||||
|
||||
if ($MessagePack) {
|
||||
$testsRun++
|
||||
if (Run-DotNetTest "QuickBenchmark.RunMessagePackComparison" "MessagePack Comparison") {
|
||||
$testsPassed++
|
||||
}
|
||||
}
|
||||
|
||||
# Summary
|
||||
Write-Host ""
|
||||
Write-Host ("=" * 80) -ForegroundColor DarkGray
|
||||
Write-Host ""
|
||||
if ($testsPassed -eq $testsRun) {
|
||||
Write-Host "All $testsRun benchmark(s) completed successfully!" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "$testsPassed of $testsRun benchmark(s) completed." -ForegroundColor Yellow
|
||||
}
|
||||
Write-Host ""
|
||||
Loading…
Reference in New Issue