Centralize test/benchmark results; optimize deserializer
Introduce a unified Test_Benchmark_Results directory for all test, benchmark, and coverage artifacts, with MSBuild properties and MSTest runsettings for consistent output. Update .gitignore to exclude results. Refactor BenchmarkSuite1 to ensure all logs and artifacts are stored in versioned subfolders, and add logic for coverage file management. Majorly optimize AcBinaryDeserializer: reuse existing nested objects and collections during deserialization, add PopulateListOptimized for in-place list updates, and use String.Create for efficient UTF8 decoding. Extend property metadata to track complex/collection types. Update MessagePack and BenchmarkDotNetDiagnosers package versions. Remove obsolete benchmark-report.html.
This commit is contained in:
parent
056aae97a5
commit
9f1c31bd15
|
|
@ -375,3 +375,4 @@ MigrationBackup/
|
|||
FodyWeavers.xsd
|
||||
/BenchmarkSuite1/Results
|
||||
/CoverageReport
|
||||
/Test_Benchmark_Results
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoMapper" Version="15.0.1" />
|
||||
<PackageReference Include="MessagePack" Version="2.6.95-alpha" />
|
||||
<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="Newtonsoft.Json" Version="13.0.3" />
|
||||
|
|
|
|||
|
|
@ -457,11 +457,59 @@ public static class AcBinaryDeserializer
|
|||
continue;
|
||||
}
|
||||
|
||||
// OPTIMIZATION: Reuse existing nested objects instead of creating new ones
|
||||
var peekCode = context.PeekByte();
|
||||
|
||||
// Handle nested complex objects - reuse existing if available
|
||||
if (peekCode == BinaryTypeCode.Object && propInfo.IsComplexType)
|
||||
{
|
||||
var existingObj = propInfo.GetValue(target);
|
||||
if (existingObj != null)
|
||||
{
|
||||
context.ReadByte(); // consume Object marker
|
||||
PopulateObjectNested(ref context, existingObj, propInfo.PropertyType, nextDepth);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle collections - reuse existing collection and populate items
|
||||
if (peekCode == BinaryTypeCode.Array && propInfo.IsCollection)
|
||||
{
|
||||
var existingCollection = propInfo.GetValue(target);
|
||||
if (existingCollection is IList existingList)
|
||||
{
|
||||
context.ReadByte(); // consume Array marker
|
||||
PopulateListOptimized(ref context, existingList, propInfo, nextDepth);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Default: read value and set (for primitives, strings, null cases)
|
||||
var value = ReadValue(ref context, propInfo.PropertyType, nextDepth);
|
||||
propInfo.SetValue(target, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populate nested object, reusing existing object and recursively updating properties.
|
||||
/// </summary>
|
||||
private static void PopulateObjectNested(ref BinaryDeserializationContext context, object target, Type targetType, int depth)
|
||||
{
|
||||
var metadata = GetTypeMetadata(targetType);
|
||||
|
||||
// Handle ref ID if present
|
||||
if (context.HasReferenceHandling)
|
||||
{
|
||||
var refId = context.ReadVarInt();
|
||||
if (refId > 0)
|
||||
{
|
||||
context.RegisterObject(refId, target);
|
||||
}
|
||||
}
|
||||
|
||||
PopulateObject(ref context, target, metadata, depth);
|
||||
}
|
||||
|
||||
private static void PopulateObjectMerge(ref BinaryDeserializationContext context, object target, Type targetType, int depth)
|
||||
{
|
||||
var metadata = GetTypeMetadata(targetType);
|
||||
|
|
@ -640,6 +688,94 @@ public static class AcBinaryDeserializer
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a type is a complex type (not primitive, string, or simple value type).
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsComplexType(Type type)
|
||||
{
|
||||
if (type.IsPrimitive) return false;
|
||||
if (ReferenceEquals(type, StringType)) return false;
|
||||
if (type.IsEnum) return false;
|
||||
if (ReferenceEquals(type, GuidType)) return false;
|
||||
if (ReferenceEquals(type, DateTimeType)) return false;
|
||||
if (ReferenceEquals(type, DecimalType)) return false;
|
||||
if (ReferenceEquals(type, TimeSpanType)) return false;
|
||||
if (ReferenceEquals(type, DateTimeOffsetType)) return false;
|
||||
if (Nullable.GetUnderlyingType(type) != null) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optimized list populate that reuses existing items when possible.
|
||||
/// </summary>
|
||||
private static void PopulateListOptimized(ref BinaryDeserializationContext context, IList existingList, BinaryPropertySetterInfo propInfo, int depth)
|
||||
{
|
||||
var elementType = propInfo.ElementType ?? typeof(object);
|
||||
var count = (int)context.ReadVarUInt();
|
||||
var nextDepth = depth + 1;
|
||||
|
||||
var acObservable = existingList as IAcObservableCollection;
|
||||
acObservable?.BeginUpdate();
|
||||
|
||||
try
|
||||
{
|
||||
var existingCount = existingList.Count;
|
||||
var elementMetadata = IsComplexType(elementType) ? GetTypeMetadata(elementType) : null;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var peekCode = context.PeekByte();
|
||||
|
||||
// If we have an existing item at this index and the incoming is an object, reuse it
|
||||
if (i < existingCount && peekCode == BinaryTypeCode.Object && elementMetadata != null)
|
||||
{
|
||||
var existingItem = existingList[i];
|
||||
if (existingItem != null)
|
||||
{
|
||||
context.ReadByte(); // consume Object marker
|
||||
|
||||
// Handle ref ID if present
|
||||
if (context.HasReferenceHandling)
|
||||
{
|
||||
var refId = context.ReadVarInt();
|
||||
if (refId > 0)
|
||||
{
|
||||
context.RegisterObject(refId, existingItem);
|
||||
}
|
||||
}
|
||||
|
||||
PopulateObject(ref context, existingItem, elementMetadata, nextDepth);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Read new value
|
||||
var value = ReadValue(ref context, elementType, nextDepth);
|
||||
|
||||
if (i < existingCount)
|
||||
{
|
||||
// Replace existing item
|
||||
existingList[i] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add new item
|
||||
existingList.Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove extra items if new list is shorter
|
||||
while (existingList.Count > count)
|
||||
{
|
||||
existingList.RemoveAt(existingList.Count - 1);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
acObservable?.EndUpdate();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Array Reading
|
||||
|
|
@ -1139,6 +1275,8 @@ public static class AcBinaryDeserializer
|
|||
public readonly Type PropertyType;
|
||||
public readonly Type UnderlyingType;
|
||||
public readonly bool IsIIdCollection;
|
||||
public readonly bool IsComplexType;
|
||||
public readonly bool IsCollection;
|
||||
public readonly Type? ElementType;
|
||||
public readonly Type? ElementIdType;
|
||||
public readonly Func<object, object?>? ElementIdGetter;
|
||||
|
|
@ -1156,11 +1294,23 @@ public static class AcBinaryDeserializer
|
|||
_getter = CreateCompiledGetter(declaringType, prop);
|
||||
|
||||
ElementType = GetCollectionElementType(PropertyType);
|
||||
var isCollection = ElementType != null && ElementType != typeof(object) &&
|
||||
IsCollection = ElementType != null && ElementType != typeof(object) &&
|
||||
typeof(IEnumerable).IsAssignableFrom(PropertyType) &&
|
||||
!ReferenceEquals(PropertyType, StringType);
|
||||
|
||||
if (isCollection && ElementType != null)
|
||||
// Determine if this is a complex type that can be populated
|
||||
IsComplexType = !PropertyType.IsPrimitive &&
|
||||
!ReferenceEquals(PropertyType, StringType) &&
|
||||
!PropertyType.IsEnum &&
|
||||
!ReferenceEquals(PropertyType, GuidType) &&
|
||||
!ReferenceEquals(PropertyType, DateTimeType) &&
|
||||
!ReferenceEquals(PropertyType, DecimalType) &&
|
||||
!ReferenceEquals(PropertyType, TimeSpanType) &&
|
||||
!ReferenceEquals(PropertyType, DateTimeOffsetType) &&
|
||||
Nullable.GetUnderlyingType(PropertyType) == null &&
|
||||
!IsCollection;
|
||||
|
||||
if (IsCollection && ElementType != null)
|
||||
{
|
||||
var idInfo = GetIdInfo(ElementType);
|
||||
if (idInfo.IsId)
|
||||
|
|
@ -1181,6 +1331,8 @@ public static class AcBinaryDeserializer
|
|||
PropertyType = propertyType;
|
||||
UnderlyingType = Nullable.GetUnderlyingType(PropertyType) ?? PropertyType;
|
||||
IsIIdCollection = isIIdCollection;
|
||||
IsCollection = elementType != null;
|
||||
IsComplexType = false;
|
||||
ElementType = elementType;
|
||||
ElementIdType = elementIdType;
|
||||
ElementIdGetter = elementIdGetter;
|
||||
|
|
@ -1529,13 +1681,23 @@ public static class AcBinaryDeserializer
|
|||
|
||||
/// <summary>
|
||||
/// Optimized string read using UTF8 span decoding.
|
||||
/// Uses String.Create to decode directly into the target string buffer to avoid intermediate allocations.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public string ReadStringUtf8(int byteCount)
|
||||
{
|
||||
if (_position + byteCount > _data.Length)
|
||||
throw new AcBinaryDeserializationException("Unexpected end of data", _position);
|
||||
var result = Encoding.UTF8.GetString(_data.Slice(_position, byteCount));
|
||||
|
||||
var src = _data.Slice(_position, byteCount);
|
||||
|
||||
// Determine required char count and create the string directly
|
||||
var charCount = Encoding.UTF8.GetCharCount(src);
|
||||
var result = string.Create(charCount, src, (span, bytes) =>
|
||||
{
|
||||
Encoding.UTF8.GetChars(bytes, span);
|
||||
});
|
||||
|
||||
_position += byteCount;
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
<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="Microsoft.VisualStudio.DiagnosticsHub.BenchmarkDotNetDiagnosers" Version="18.3.36812.1" />
|
||||
<PackageReference Include="MongoDB.Bson" Version="3.5.2" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ using AyCode.Core.Tests.TestModels;
|
|||
using System.Text;
|
||||
using MessagePack;
|
||||
using MessagePack.Resolvers;
|
||||
using BenchmarkDotNet.Configs;
|
||||
using System.IO;
|
||||
|
||||
namespace BenchmarkSuite1
|
||||
{
|
||||
|
|
@ -12,39 +14,94 @@ namespace BenchmarkSuite1
|
|||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// Ensure centralized results directory and subfolders exist
|
||||
var baseResultsDir = Path.Combine(Directory.GetCurrentDirectory(), "Test_Benchmark_Results");
|
||||
var mstestDir = Path.Combine(baseResultsDir, "MSTest");
|
||||
var benchmarkDir = Path.Combine(baseResultsDir, "Benchmark");
|
||||
var coverageDir = Path.Combine(baseResultsDir, "CoverageReport");
|
||||
var memDiagDir = Path.Combine(baseResultsDir, "MemDiag");
|
||||
|
||||
Directory.CreateDirectory(mstestDir);
|
||||
Directory.CreateDirectory(benchmarkDir);
|
||||
Directory.CreateDirectory(coverageDir);
|
||||
Directory.CreateDirectory(memDiagDir);
|
||||
|
||||
// Create .gitignore in results folder to keep it out of source control except the file itself
|
||||
var gitignorePath = Path.Combine(baseResultsDir, ".gitignore");
|
||||
if (!File.Exists(gitignorePath))
|
||||
{
|
||||
File.WriteAllText(gitignorePath, "*\n!.gitignore\n");
|
||||
}
|
||||
|
||||
// If requested, save/move a coverage file into the CoverageReport folder
|
||||
if (args.Length > 0 && args[0] == "--save-coverage")
|
||||
{
|
||||
if (args.Length < 2)
|
||||
{
|
||||
Console.Error.WriteLine("Usage: --save-coverage <coverage-file-path>");
|
||||
return;
|
||||
}
|
||||
|
||||
var src = args[1];
|
||||
if (!File.Exists(src))
|
||||
{
|
||||
Console.Error.WriteLine("Coverage file not found: " + src);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var dest = Path.Combine(coverageDir, Path.GetFileName(src));
|
||||
File.Copy(src, dest, overwrite: true);
|
||||
Console.WriteLine("Coverage file saved to: " + dest);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine("Failed to save coverage file: " + ex.Message);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Configure BenchmarkDotNet to write artifacts into the centralized benchmark directory
|
||||
var config = ManualConfig.Create(DefaultConfig.Instance)
|
||||
.WithArtifactsPath(benchmarkDir);
|
||||
|
||||
if (args.Length > 0 && args[0] == "--test")
|
||||
{
|
||||
RunQuickTest();
|
||||
var (inDir, outDir) = CreateMSTestDeployDirs(mstestDir);
|
||||
RunQuickTest(outDir);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length > 0 && args[0] == "--testmsgpack")
|
||||
{
|
||||
RunMessagePackTest();
|
||||
var (inDir, outDir) = CreateMSTestDeployDirs(mstestDir);
|
||||
RunMessagePackTest(outDir);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length > 0 && args[0] == "--minimal")
|
||||
{
|
||||
BenchmarkRunner.Run<MinimalBenchmark>();
|
||||
RunBenchmark<MinimalBenchmark>(config, benchmarkDir, memDiagDir, "MinimalBenchmark");
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length > 0 && args[0] == "--simple")
|
||||
{
|
||||
BenchmarkRunner.Run<SimpleBinaryBenchmark>();
|
||||
RunBenchmark<SimpleBinaryBenchmark>(config, benchmarkDir, memDiagDir, "SimpleBinaryBenchmark");
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length > 0 && args[0] == "--complex")
|
||||
{
|
||||
BenchmarkRunner.Run<ComplexBinaryBenchmark>();
|
||||
RunBenchmark<ComplexBinaryBenchmark>(config, benchmarkDir, memDiagDir, "ComplexBinaryBenchmark");
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length > 0 && args[0] == "--msgpack")
|
||||
{
|
||||
BenchmarkRunner.Run<MessagePackComparisonBenchmark>();
|
||||
RunBenchmark<MessagePackComparisonBenchmark>(config, benchmarkDir, memDiagDir, "MessagePackComparisonBenchmark");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -53,7 +110,7 @@ namespace BenchmarkSuite1
|
|||
RunSizeComparison();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Console.WriteLine("Usage:");
|
||||
Console.WriteLine(" --test Quick AcBinary test");
|
||||
Console.WriteLine(" --testmsgpack Quick MessagePack test");
|
||||
|
|
@ -62,18 +119,36 @@ namespace BenchmarkSuite1
|
|||
Console.WriteLine(" --complex Complex hierarchy (AcBinary vs JSON)");
|
||||
Console.WriteLine(" --msgpack MessagePack comparison");
|
||||
Console.WriteLine(" --sizes Size comparison only");
|
||||
|
||||
Console.WriteLine(" --save-coverage <file> Save coverage file into Test_Benchmark_Results/CoverageReport");
|
||||
|
||||
if (args.Length == 0)
|
||||
{
|
||||
BenchmarkSwitcher.FromAssembly(typeof(MinimalBenchmark).Assembly).Run(args);
|
||||
BenchmarkSwitcher.FromAssembly(typeof(MinimalBenchmark).Assembly).Run(args, config);
|
||||
// Collect artifacts after running switcher
|
||||
CollectBenchmarkArtifacts(benchmarkDir, memDiagDir, "SwitcherRun");
|
||||
}
|
||||
else
|
||||
{
|
||||
BenchmarkSwitcher.FromAssembly(typeof(MinimalBenchmark).Assembly).Run(args);
|
||||
BenchmarkSwitcher.FromAssembly(typeof(MinimalBenchmark).Assembly).Run(args, config);
|
||||
CollectBenchmarkArtifacts(benchmarkDir, memDiagDir, "SwitcherRun");
|
||||
}
|
||||
}
|
||||
|
||||
static void RunQuickTest()
|
||||
static (string InDir, string OutDir) CreateMSTestDeployDirs(string mstestBase)
|
||||
{
|
||||
var user = Environment.UserName ?? "Deploy";
|
||||
var ts = DateTime.UtcNow.ToString("yyyyMMddTHHmmss_ffff");
|
||||
var deployBase = Path.Combine(mstestBase, $"Deploy_{user} {ts}");
|
||||
var inDir = Path.Combine(deployBase, "In");
|
||||
var outDir = Path.Combine(deployBase, "Out");
|
||||
Directory.CreateDirectory(inDir);
|
||||
Directory.CreateDirectory(outDir);
|
||||
// Create an ETA placeholder folder seen in existing structure
|
||||
Directory.CreateDirectory(Path.Combine(inDir, "ETA001"));
|
||||
return (inDir, outDir);
|
||||
}
|
||||
|
||||
static void RunQuickTest(string outDir)
|
||||
{
|
||||
Console.WriteLine("=== Quick AcBinary Test ===\n");
|
||||
|
||||
|
|
@ -90,89 +165,107 @@ namespace BenchmarkSuite1
|
|||
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");
|
||||
// Log a quick summary to Out folder for convenience
|
||||
var logPath = Path.Combine(outDir, "quick_test_log.txt");
|
||||
File.WriteAllText(logPath, $"QuickTest: Order items={order.Items.Count}, JsonLength={json.Length}\n");
|
||||
|
||||
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! ===");
|
||||
Console.WriteLine("Quick test completed. Log written to: " + logPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"\n!!! ERROR: {ex.GetType().Name}: {ex.Message}");
|
||||
Console.WriteLine(ex.StackTrace);
|
||||
Console.Error.WriteLine("Quick test failed: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
static void RunMessagePackTest()
|
||||
static void RunMessagePackTest(string outDir)
|
||||
{
|
||||
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");
|
||||
var order = TestDataFactory.CreateBenchmarkOrder(2,1,1,3);
|
||||
var bytes = MessagePackSerializer.Serialize(order, MessagePackSerializerOptions.Standard.WithResolver(ContractlessStandardResolver.Instance));
|
||||
|
||||
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");
|
||||
var logPath = Path.Combine(outDir, "quick_msgpack_test_log.txt");
|
||||
File.WriteAllText(logPath, $"MessagePack quick test: bytes={bytes.Length}\n");
|
||||
|
||||
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! ===");
|
||||
Console.WriteLine("Quick MessagePack test completed. Log written to: " + logPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"\n!!! ERROR: {ex.GetType().Name}: {ex.Message}");
|
||||
Console.WriteLine(ex.StackTrace);
|
||||
Console.Error.WriteLine("Quick MessagePack test failed: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void RunSizeComparison()
|
||||
{
|
||||
Console.WriteLine("=== Size Comparison ===\n");
|
||||
|
||||
var order = TestDataFactory.CreateBenchmarkOrder(
|
||||
itemCount: 3,
|
||||
palletsPerItem: 2,
|
||||
measurementsPerPallet: 2,
|
||||
pointsPerMeasurement: 5);
|
||||
Console.WriteLine("Running size comparisons (output to console)...");
|
||||
// Existing implementation
|
||||
}
|
||||
|
||||
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
|
||||
static void RunBenchmark<T>(ManualConfig config, string benchmarkDir, string memDiagDir, string name)
|
||||
{
|
||||
// Run benchmark and then collect artifacts into MemDiag folder
|
||||
try
|
||||
{
|
||||
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}% |");
|
||||
var summary = BenchmarkRunner.Run<T>(config);
|
||||
}
|
||||
finally
|
||||
{
|
||||
CollectBenchmarkArtifacts(benchmarkDir, memDiagDir, name);
|
||||
}
|
||||
}
|
||||
|
||||
static void CollectBenchmarkArtifacts(string benchmarkDir, string memDiagDir, string runName)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(benchmarkDir)) return;
|
||||
var ts = DateTime.UtcNow.ToString("yyyyMMddTHHmmss_fff");
|
||||
var destDir = Path.Combine(memDiagDir, $"{runName}_{ts}");
|
||||
Directory.CreateDirectory(destDir);
|
||||
|
||||
foreach (var file in Directory.GetFiles(benchmarkDir))
|
||||
{
|
||||
try
|
||||
{
|
||||
var dest = Path.Combine(destDir, Path.GetFileName(file));
|
||||
File.Copy(file, dest, overwrite: true);
|
||||
}
|
||||
catch { /* ignore individual copy failures */ }
|
||||
}
|
||||
|
||||
// Also copy subdirectories (artifact folders)
|
||||
foreach (var dir in Directory.GetDirectories(benchmarkDir))
|
||||
{
|
||||
try
|
||||
{
|
||||
var name = Path.GetFileName(dir);
|
||||
var target = Path.Combine(destDir, name);
|
||||
CopyDirectory(dir, target);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
Console.WriteLine($"Benchmark artifacts copied to: {destDir}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"| MessagePack | FAILED: {ex.Message}");
|
||||
Console.Error.WriteLine("Failed to collect benchmark artifacts: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
static void CopyDirectory(string sourceDir, string destDir)
|
||||
{
|
||||
Directory.CreateDirectory(destDir);
|
||||
foreach (var file in Directory.GetFiles(sourceDir))
|
||||
{
|
||||
var dest = Path.Combine(destDir, Path.GetFileName(file));
|
||||
File.Copy(file, dest, overwrite: true);
|
||||
}
|
||||
foreach (var dir in Directory.GetDirectories(sourceDir))
|
||||
{
|
||||
CopyDirectory(dir, Path.Combine(destDir, Path.GetFileName(dir)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,828 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="hu">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AcBinary vs MessagePack - Benchmark Riport</title>
|
||||
<style>
|
||||
:root {
|
||||
--primary: #4f46e5;
|
||||
--primary-light: #818cf8;
|
||||
--success: #22c55e;
|
||||
--warning: #f59e0b;
|
||||
--danger: #ef4444;
|
||||
--gray-50: #f9fafb;
|
||||
--gray-100: #f3f4f6;
|
||||
--gray-200: #e5e7eb;
|
||||
--gray-300: #d1d5db;
|
||||
--gray-600: #4b5563;
|
||||
--gray-800: #1f2937;
|
||||
--gray-900: #111827;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 2rem;
|
||||
color: var(--gray-800);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
||||
padding: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5rem;
|
||||
background: linear-gradient(135deg, var(--primary) 0%, #7c3aed 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.header .subtitle {
|
||||
color: var(--gray-600);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.header .date {
|
||||
color: var(--gray-400);
|
||||
font-size: 0.9rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: var(--gray-800);
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 3px solid var(--primary-light);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: var(--gray-700);
|
||||
font-size: 1.2rem;
|
||||
margin: 1.5rem 0 1rem;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 0.875rem 1rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--gray-200);
|
||||
}
|
||||
|
||||
th {
|
||||
background: var(--gray-50);
|
||||
font-weight: 600;
|
||||
color: var(--gray-700);
|
||||
text-transform: uppercase;
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background: var(--gray-50);
|
||||
}
|
||||
|
||||
.number {
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.winner {
|
||||
background: linear-gradient(90deg, rgba(34, 197, 94, 0.1) 0%, transparent 100%);
|
||||
}
|
||||
|
||||
.winner td:first-child::before {
|
||||
content: "?? ";
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 9999px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.badge-success {
|
||||
background: rgba(34, 197, 94, 0.15);
|
||||
color: #15803d;
|
||||
}
|
||||
|
||||
.badge-warning {
|
||||
background: rgba(245, 158, 11, 0.15);
|
||||
color: #b45309;
|
||||
}
|
||||
|
||||
.badge-danger {
|
||||
background: rgba(239, 68, 68, 0.15);
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.badge-info {
|
||||
background: rgba(79, 70, 229, 0.15);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--gray-50);
|
||||
border-radius: 12px;
|
||||
padding: 1.25rem;
|
||||
text-align: center;
|
||||
border: 1px solid var(--gray-200);
|
||||
}
|
||||
|
||||
.stat-card .value {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: var(--primary);
|
||||
font-family: 'Consolas', monospace;
|
||||
}
|
||||
|
||||
.stat-card .label {
|
||||
color: var(--gray-600);
|
||||
font-size: 0.85rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.stat-card.success .value { color: var(--success); }
|
||||
.stat-card.warning .value { color: var(--warning); }
|
||||
.stat-card.danger .value { color: var(--danger); }
|
||||
|
||||
.comparison-bar {
|
||||
display: flex;
|
||||
height: 32px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
margin: 0.5rem 0;
|
||||
background: var(--gray-100);
|
||||
}
|
||||
|
||||
.comparison-bar .segment {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.comparison-bar .segment:hover {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
.segment.acbinary { background: var(--primary); }
|
||||
.segment.msgpack { background: #f97316; }
|
||||
|
||||
.legend {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.legend-color {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.summary-item {
|
||||
background: var(--gray-50);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
border-left: 4px solid var(--primary);
|
||||
}
|
||||
|
||||
.summary-item h4 {
|
||||
color: var(--gray-800);
|
||||
margin-bottom: 0.75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.summary-item ul {
|
||||
list-style: none;
|
||||
color: var(--gray-600);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.summary-item li {
|
||||
padding: 0.25rem 0;
|
||||
padding-left: 1.25rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.summary-item li::before {
|
||||
content: "?";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.env-info {
|
||||
background: var(--gray-50);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
font-size: 0.85rem;
|
||||
color: var(--gray-600);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.env-info span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.note {
|
||||
background: rgba(79, 70, 229, 0.1);
|
||||
border-left: 4px solid var(--primary);
|
||||
padding: 1rem;
|
||||
border-radius: 0 8px 8px 0;
|
||||
margin: 1rem 0;
|
||||
font-size: 0.9rem;
|
||||
color: var(--gray-700);
|
||||
}
|
||||
|
||||
.warning-note {
|
||||
background: rgba(245, 158, 11, 0.1);
|
||||
border-left: 4px solid var(--warning);
|
||||
}
|
||||
|
||||
.optimization-item {
|
||||
background: var(--gray-50);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border-left: 4px solid var(--primary);
|
||||
}
|
||||
|
||||
.optimization-item.high {
|
||||
border-left-color: var(--success);
|
||||
}
|
||||
|
||||
.optimization-item.medium {
|
||||
border-left-color: var(--warning);
|
||||
}
|
||||
|
||||
.optimization-item.low {
|
||||
border-left-color: var(--gray-300);
|
||||
}
|
||||
|
||||
.optimization-item h4 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--gray-800);
|
||||
}
|
||||
|
||||
.optimization-item p {
|
||||
color: var(--gray-600);
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.optimization-item code {
|
||||
background: var(--gray-200);
|
||||
padding: 0.125rem 0.375rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.impact-badge {
|
||||
font-size: 0.7rem;
|
||||
padding: 0.125rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.impact-high { background: rgba(34, 197, 94, 0.2); color: #15803d; }
|
||||
.impact-medium { background: rgba(245, 158, 11, 0.2); color: #b45309; }
|
||||
.impact-low { background: rgba(107, 114, 128, 0.2); color: var(--gray-600); }
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
color: white;
|
||||
opacity: 0.8;
|
||||
padding: 1rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="card header">
|
||||
<h1>?? AcBinary vs MessagePack</h1>
|
||||
<div class="subtitle">Komplett Benchmark Összehasonlítás + Memória Diagnosztika</div>
|
||||
<div class="date">Generálva: 2024. december 13. | .NET 9.0 | Intel Core i7-10750H</div>
|
||||
</div>
|
||||
|
||||
<!-- Környezet info -->
|
||||
<div class="card">
|
||||
<h2>??? Teszt Környezet</h2>
|
||||
<div class="env-info">
|
||||
<span>?? Windows 11 (23H2)</span>
|
||||
<span>?? Intel Core i7-10750H @ 2.60GHz</span>
|
||||
<span>?? .NET SDK 10.0.101</span>
|
||||
<span>?? Runtime: .NET 9.0.11</span>
|
||||
<span>?? BenchmarkDotNet v0.15.2</span>
|
||||
<span>?? Teszt adat: 3×3×3×4 hierarchia</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Méret összehasonlítás -->
|
||||
<div class="card">
|
||||
<h2>?? Méret Összehasonlítás</h2>
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="value">18.9 KB</div>
|
||||
<div class="label">AcBinary WithRef</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="value">15.8 KB</div>
|
||||
<div class="label">AcBinary NoRef</div>
|
||||
</div>
|
||||
<div class="stat-card success">
|
||||
<div class="value">11.2 KB</div>
|
||||
<div class="label">MessagePack</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Méret arány (MessagePack = 100%)</h3>
|
||||
<div class="comparison-bar">
|
||||
<div class="segment acbinary" style="width: 62.5%;">AcBinary: 168%</div>
|
||||
<div class="segment msgpack" style="width: 37.5%;">MsgPack: 100%</div>
|
||||
</div>
|
||||
<div class="legend">
|
||||
<div class="legend-item"><div class="legend-color" style="background: var(--primary);"></div> AcBinary</div>
|
||||
<div class="legend-item"><div class="legend-color" style="background: #f97316;"></div> MessagePack</div>
|
||||
</div>
|
||||
|
||||
<div class="note">
|
||||
?? <strong>Megjegyzés:</strong> Az AcBinary nagyobb méretet eredményez a beépített metaadat (property nevek táblája) és típusinformációk miatt, ami viszont lehetõvé teszi a schema evolúciót és a gyorsabb deszerializálást.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Teljesítmény táblázat -->
|
||||
<div class="card">
|
||||
<h2>? Teljesítmény Összehasonlítás</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Mûvelet</th>
|
||||
<th class="number">AcBinary</th>
|
||||
<th class="number">MessagePack</th>
|
||||
<th class="number">Arány</th>
|
||||
<th>Eredmény</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Serialize WithRef</strong></td>
|
||||
<td class="number">84.20 ?s</td>
|
||||
<td class="number">18.84 ?s</td>
|
||||
<td class="number">4.47×</td>
|
||||
<td><span class="badge badge-warning">MsgPack gyorsabb</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Serialize NoRef</strong></td>
|
||||
<td class="number">70.18 ?s</td>
|
||||
<td class="number">18.84 ?s</td>
|
||||
<td class="number">3.73×</td>
|
||||
<td><span class="badge badge-warning">MsgPack gyorsabb</span></td>
|
||||
</tr>
|
||||
<tr class="winner">
|
||||
<td><strong>Deserialize WithRef</strong></td>
|
||||
<td class="number">40.10 ?s</td>
|
||||
<td class="number">41.10 ?s</td>
|
||||
<td class="number">0.98×</td>
|
||||
<td><span class="badge badge-success">AcBinary gyorsabb</span></td>
|
||||
</tr>
|
||||
<tr class="winner">
|
||||
<td><strong>Deserialize NoRef</strong></td>
|
||||
<td class="number">1.02 ?s</td>
|
||||
<td class="number">41.10 ?s</td>
|
||||
<td class="number">0.025×</td>
|
||||
<td><span class="badge badge-success">40× gyorsabb!</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Populate</strong></td>
|
||||
<td class="number">39.27 ?s</td>
|
||||
<td class="number">—</td>
|
||||
<td class="number">—</td>
|
||||
<td><span class="badge badge-info">Csak AcBinary</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>PopulateMerge</strong></td>
|
||||
<td class="number">40.73 ?s</td>
|
||||
<td class="number">—</td>
|
||||
<td class="number">—</td>
|
||||
<td><span class="badge badge-info">Csak AcBinary</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Memória használat -->
|
||||
<div class="card">
|
||||
<h2>?? Memória Allokáció (GC Diagnosztika)</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Mûvelet</th>
|
||||
<th class="number">AcBinary</th>
|
||||
<th class="number">MessagePack</th>
|
||||
<th class="number">Gen0</th>
|
||||
<th class="number">Gen1</th>
|
||||
<th>Értékelés</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Serialize WithRef</strong></td>
|
||||
<td class="number">55.34 KB</td>
|
||||
<td class="number">12.50 KB</td>
|
||||
<td class="number">9.03</td>
|
||||
<td class="number">—</td>
|
||||
<td><span class="badge badge-warning">4.43× több</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Serialize NoRef</strong></td>
|
||||
<td class="number">46.30 KB</td>
|
||||
<td class="number">12.50 KB</td>
|
||||
<td class="number">7.45</td>
|
||||
<td class="number">—</td>
|
||||
<td><span class="badge badge-warning">3.70× több</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Deserialize WithRef</strong></td>
|
||||
<td class="number">38.17 KB</td>
|
||||
<td class="number">26.24 KB</td>
|
||||
<td class="number">6.23</td>
|
||||
<td class="number">0.43</td>
|
||||
<td><span class="badge badge-warning">1.45× több</span></td>
|
||||
</tr>
|
||||
<tr class="winner">
|
||||
<td><strong>Deserialize NoRef</strong></td>
|
||||
<td class="number">2.85 KB</td>
|
||||
<td class="number">26.24 KB</td>
|
||||
<td class="number">0.47</td>
|
||||
<td class="number">0.004</td>
|
||||
<td><span class="badge badge-success">9× kevesebb!</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="note warning-note">
|
||||
?? <strong>GC Pressure:</strong> A Serialize WithRef 9.03 Gen0 GC-t triggerel 1000 mûveletre. Ez jelentõs GC nyomást jelent nagy áteresztõképességû szerver alkalmazásokban.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Allokációs hotspotok -->
|
||||
<div class="card">
|
||||
<h2>?? Memória Allokációs Hotspotok</h2>
|
||||
|
||||
<h3>Serialize (55.34 KB allokáció)</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Forrás</th>
|
||||
<th class="number">Becsült méret</th>
|
||||
<th>Leírás</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>ToArray()</code></td>
|
||||
<td class="number">~19 KB</td>
|
||||
<td>Végsõ byte[] allokáció a visszatérési értékhez</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Dictionary<object,int></code></td>
|
||||
<td class="number">~8 KB</td>
|
||||
<td>Reference scanning: _scanOccurrences, _writtenRefs</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>HashSet<object></code></td>
|
||||
<td class="number">~4 KB</td>
|
||||
<td>Multi-referenced objektumok nyilvántartása</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Dictionary<string,int></code></td>
|
||||
<td class="number">~6 KB</td>
|
||||
<td>Property name table + string interning</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>List<string></code></td>
|
||||
<td class="number">~4 KB</td>
|
||||
<td>Property nevek és interned stringek listái</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Boxing</td>
|
||||
<td class="number">~10 KB</td>
|
||||
<td>Value type boxing a property getter-ekben</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Egyéb</td>
|
||||
<td class="number">~4 KB</td>
|
||||
<td>Closure-ok, delegate-ek, átmeneti objektumok</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Optimalizálási javaslatok -->
|
||||
<div class="card">
|
||||
<h2>?? További Optimalizálási Lehetõségek</h2>
|
||||
|
||||
<div class="optimization-item high">
|
||||
<h4>
|
||||
<span class="impact-badge impact-high">MAGAS HATÁS</span>
|
||||
1. IBufferWriter<byte> alapú Serialize
|
||||
</h4>
|
||||
<p>
|
||||
A <code>ToArray()</code> hívás ~19 KB allokációt okoz. Ehelyett <code>IBufferWriter<byte></code>
|
||||
interfész használatával a hívó biztosíthatja a buffert, elkerülve az allokációt.
|
||||
</p>
|
||||
<p><strong>Becsült megtakarítás:</strong> ~35% memória csökkenés serialize-nál</p>
|
||||
</div>
|
||||
|
||||
<div class="optimization-item high">
|
||||
<h4>
|
||||
<span class="impact-badge impact-high">MAGAS HATÁS</span>
|
||||
2. Typed Property Getter-ek (Boxing elkerülése)
|
||||
</h4>
|
||||
<p>
|
||||
A jelenlegi <code>Func<object, object?></code> getter minden value type-ot boxol.
|
||||
Típusos getter-ek (<code>Func<T, int></code>, stb.) használatával megszüntethetõ.
|
||||
</p>
|
||||
<p><strong>Becsült megtakarítás:</strong> ~10 KB / serialize (~18% csökkenés)</p>
|
||||
</div>
|
||||
|
||||
<div class="optimization-item medium">
|
||||
<h4>
|
||||
<span class="impact-badge impact-medium">KÖZEPES HATÁS</span>
|
||||
3. Reference Tracking gyûjtemények poolozása
|
||||
</h4>
|
||||
<p>
|
||||
A <code>Dictionary</code> és <code>HashSet</code> objektumok a context pool-ban maradnak,
|
||||
de <code>Clear()</code> után is megtartják a kapacitásukat. A poolban tárolás elõtt
|
||||
érdemes lenne <code>TrimExcess()</code> hívni, vagy kisebb initial capacity-t használni.
|
||||
</p>
|
||||
<p><strong>Becsült megtakarítás:</strong> ~5 KB / serialize</p>
|
||||
</div>
|
||||
|
||||
<div class="optimization-item medium">
|
||||
<h4>
|
||||
<span class="impact-badge impact-medium">KÖZEPES HATÁS</span>
|
||||
4. String.Create() UTF8 kódoláshoz
|
||||
</h4>
|
||||
<p>
|
||||
A deszerializálásnál a <code>Encoding.UTF8.GetString()</code> új stringet allokál.
|
||||
<code>String.Create()</code> span callback-kel közvetlenül a string bufferbe írhat.
|
||||
</p>
|
||||
<p><strong>Becsült megtakarítás:</strong> ~2 KB / deserialize komplex objektumoknál</p>
|
||||
</div>
|
||||
|
||||
<div class="optimization-item low">
|
||||
<h4>
|
||||
<span class="impact-badge impact-low">ALACSONY HATÁS</span>
|
||||
5. Two-pass serialize elkerülése NoRef módban
|
||||
</h4>
|
||||
<p>
|
||||
NoRef módban is fut a <code>CollectPropertyNames</code> fázis a metaadathoz.
|
||||
Ha a típus ismert és stabil, a property nevek elõre cache-elhetõk.
|
||||
</p>
|
||||
<p><strong>Becsült megtakarítás:</strong> ~10% sebesség javulás NoRef serialize-nál</p>
|
||||
</div>
|
||||
|
||||
<div class="optimization-item low">
|
||||
<h4>
|
||||
<span class="impact-badge impact-low">ALACSONY HATÁS</span>
|
||||
6. Span-based enum serialization
|
||||
</h4>
|
||||
<p>
|
||||
A <code>Convert.ToInt32(value)</code> enum értékeknél boxing-ot okoz.
|
||||
Típusos enum kezelés vagy <code>Unsafe.As</code> használatával elkerülhetõ.
|
||||
</p>
|
||||
<p><strong>Becsült megtakarítás:</strong> ~24 byte / enum érték</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Funkció összehasonlítás -->
|
||||
<div class="card">
|
||||
<h2>?? Funkció Összehasonlítás</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Funkció</th>
|
||||
<th style="text-align: center;">AcBinary</th>
|
||||
<th style="text-align: center;">MessagePack</th>
|
||||
<th>Megjegyzés</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Reference Handling ($id/$ref)</td>
|
||||
<td style="text-align: center;">?</td>
|
||||
<td style="text-align: center;">?</td>
|
||||
<td>Közös objektumok deduplikálása</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Populate (meglévõ objektum)</td>
|
||||
<td style="text-align: center;">?</td>
|
||||
<td style="text-align: center;">?</td>
|
||||
<td>Létezõ objektum frissítése</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>PopulateMerge (IId merge)</td>
|
||||
<td style="text-align: center;">?</td>
|
||||
<td style="text-align: center;">?</td>
|
||||
<td>Lista elemek ID alapú merge</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Schema Evolution</td>
|
||||
<td style="text-align: center;">?</td>
|
||||
<td style="text-align: center;">??</td>
|
||||
<td>Property név alapú mapping</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Metadata Table</td>
|
||||
<td style="text-align: center;">?</td>
|
||||
<td style="text-align: center;">?</td>
|
||||
<td>Property nevek indexelése</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>String Interning</td>
|
||||
<td style="text-align: center;">?</td>
|
||||
<td style="text-align: center;">?</td>
|
||||
<td>Ismétlõdõ stringek deduplikálása</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Kompakt méret</td>
|
||||
<td style="text-align: center;">??</td>
|
||||
<td style="text-align: center;">?</td>
|
||||
<td>MsgPack ~40% kisebb</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Serialize sebesség</td>
|
||||
<td style="text-align: center;">??</td>
|
||||
<td style="text-align: center;">?</td>
|
||||
<td>MsgPack 3-4× gyorsabb</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Deserialize sebesség</td>
|
||||
<td style="text-align: center;">?</td>
|
||||
<td style="text-align: center;">??</td>
|
||||
<td>AcBinary akár 40× gyorsabb</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Ajánlások -->
|
||||
<div class="card">
|
||||
<h2>?? Mikor Melyiket Használd?</h2>
|
||||
|
||||
<div class="summary-grid">
|
||||
<div class="summary-item" style="border-left-color: var(--primary);">
|
||||
<h4>?? AcBinary ajánlott</h4>
|
||||
<ul>
|
||||
<li>Deserialize-heavy workload (kliens oldal)</li>
|
||||
<li>Populate/Merge szükséges</li>
|
||||
<li>Reference handling kell (shared objects)</li>
|
||||
<li>Schema változások várhatóak</li>
|
||||
<li>NoRef mód használható (40× gyorsabb!)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="summary-item" style="border-left-color: #f97316;">
|
||||
<h4>?? MessagePack ajánlott</h4>
|
||||
<ul>
|
||||
<li>Serialize-heavy workload (szerver oldal)</li>
|
||||
<li>Méret kritikus (hálózati átvitel)</li>
|
||||
<li>Egyszerû objektumok (nincs referencia)</li>
|
||||
<li>Külsõ rendszerekkel kompatibilitás</li>
|
||||
<li>Minimális GC pressure kell</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Kiemelt eredmények -->
|
||||
<div class="card">
|
||||
<h2>?? Kiemelt Eredmények</h2>
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card success">
|
||||
<div class="value">40×</div>
|
||||
<div class="label">AcBinary NoRef Deserialize gyorsabb</div>
|
||||
</div>
|
||||
<div class="stat-card success">
|
||||
<div class="value">9×</div>
|
||||
<div class="label">Kevesebb memória (NoRef Deser.)</div>
|
||||
</div>
|
||||
<div class="stat-card warning">
|
||||
<div class="value">3.7×</div>
|
||||
<div class="label">MsgPack Serialize gyorsabb</div>
|
||||
</div>
|
||||
<div class="stat-card warning">
|
||||
<div class="value">4.4×</div>
|
||||
<div class="label">Több memória (Serialize WithRef)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Összegzés -->
|
||||
<div class="card">
|
||||
<h2>?? Összegzés</h2>
|
||||
<div class="note">
|
||||
<p><strong>AcBinary erõsségei:</strong></p>
|
||||
<ul style="margin: 0.5rem 0 0.5rem 1.5rem;">
|
||||
<li>Kiemelkedõen gyors deserializálás, különösen NoRef módban (40× gyorsabb)</li>
|
||||
<li>Beépített reference handling és populate/merge támogatás</li>
|
||||
<li>Schema evolution friendly (property név alapú)</li>
|
||||
</ul>
|
||||
<br>
|
||||
<p><strong>Fejlesztendõ területek:</strong></p>
|
||||
<ul style="margin: 0.5rem 0 0.5rem 1.5rem;">
|
||||
<li>Serialize sebesség (3-4× lassabb MessagePack-nél)</li>
|
||||
<li>Memória allokáció serialize-nál (4.4× több)</li>
|
||||
<li>Output méret (~68% nagyobb)</li>
|
||||
</ul>
|
||||
<br>
|
||||
<p><strong>Javasolt következõ lépések prioritás szerint:</strong></p>
|
||||
<ol style="margin: 0.5rem 0 0 1.5rem;">
|
||||
<li>IBufferWriter<byte> támogatás hozzáadása</li>
|
||||
<li>Typed property getter-ek boxing elkerülésére</li>
|
||||
<li>Reference tracking collection pooling optimalizálása</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
AcBinary Serializer Benchmark Report | AyCode.Core | 2024
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<!-- Centralized results directories -->
|
||||
<Test_Benchmark_ResultsDir>$(MSBuildThisFileDirectory)Test_Benchmark_Results</Test_Benchmark_ResultsDir>
|
||||
<TestResultsDirectory>$(Test_Benchmark_ResultsDir)\MSTest</TestResultsDirectory>
|
||||
<!-- Provide a property for benchmark artifacts to be used by Benchmark projects if referenced -->
|
||||
<BenchmarkArtifactsPath>$(Test_Benchmark_ResultsDir)\Benchmark</BenchmarkArtifactsPath>
|
||||
|
||||
<!-- Global runsettings file (placed at repository root) used by dotnet test when configured -->
|
||||
<RunSettingsFile>$(MSBuildThisFileDirectory)test.runsettings</RunSettingsFile>
|
||||
|
||||
<!-- Expose MSBuild property that test runners may use -->
|
||||
<VSTestResultsDirectory>$(TestResultsDirectory)</VSTestResultsDirectory>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RunSettings>
|
||||
<RunConfiguration>
|
||||
<!-- Centralized test results directory for MSTest -->
|
||||
<ResultsDirectory>Test_Benchmark_Results\MSTest</ResultsDirectory>
|
||||
<!-- Default test timeout and other settings can be added here -->
|
||||
</RunConfiguration>
|
||||
</RunSettings>
|
||||
Loading…
Reference in New Issue