[LOADED_DOCS: 3 files, no new loads]
NativeAOT: fallback for delegates, exclude MessagePack Added AYCODE_NATIVEAOT symbol for AOT builds and excluded MessagePack benchmarks from NativeAOT due to lack of AOT support. Updated AcSerializerCommon to use reflection-based delegates when dynamic code is unavailable, ensuring compatibility with both JIT and AOT. Added explanatory comments throughout.
This commit is contained in:
parent
97ac3e21a3
commit
9b8e56557f
|
|
@ -21,6 +21,12 @@
|
|||
<PublishAot>true</PublishAot>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
|
||||
<!-- AYCODE_NATIVEAOT compile symbol — defined whenever PublishAot=true is in effect.
|
||||
Used by Program.cs to #if-out MessagePack benchmark sites: MessagePack v3 has no AOT-compatible
|
||||
resolver in this project's setup (DynamicGenericResolver fails on trimmed ListFormatter<T>).
|
||||
This is a benchmark-project-local workaround and never ships as NuGet — directives are safe here. -->
|
||||
<DefineConstants>$(DefineConstants);AYCODE_NATIVEAOT</DefineConstants>
|
||||
|
||||
<!-- Először tegyük zsongva: nyeljük le a trim warning-okat hogy buildelni tudjon. -->
|
||||
<!-- Ezt később vissza lehet kapcsolni szigorúra. -->
|
||||
<SuppressTrimAnalysisWarnings>true</SuppressTrimAnalysisWarnings>
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@ using AyCode.Core.Serializers.Binaries;
|
|||
using AyCode.Core.Tests.Serialization; // DrainFromAsync extension (test-only, used by benchmark)
|
||||
using AyCode.Core.Tests.TestModels;
|
||||
using MemoryPack;
|
||||
#if !AYCODE_NATIVEAOT
|
||||
using MessagePack;
|
||||
using MessagePack.Resolvers;
|
||||
#endif
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Buffers;
|
||||
using System.Diagnostics;
|
||||
|
|
@ -52,7 +54,9 @@ public static class Program
|
|||
// Engine identifiers (used in Engine column + comparison logic)
|
||||
private const string EngineAcBinary = "AcBinary";
|
||||
private const string EngineMemoryPack = "MemoryPack";
|
||||
#if !AYCODE_NATIVEAOT
|
||||
private const string EngineMessagePack = "MessagePack";
|
||||
#endif
|
||||
private const string EngineSystemTextJson = "System.Text.Json";
|
||||
|
||||
// IO mode identifiers (used in IO column + comparison logic)
|
||||
|
|
@ -557,9 +561,9 @@ public static class Program
|
|||
new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.FastMode, "FastMode"),
|
||||
// Fastest Byte[] — Runtime path (UseGeneratedCode=false). Same wire/options, no source-generated dispatch.
|
||||
// Always paired with the SGen variant so every layer can compare the SGen speed-up apples-to-apples.
|
||||
// COMMENTED: Reflection.Emit-based dispatch crashes under NativeAOT (PlatformNotSupportedException).
|
||||
// Re-enable for JIT-mode benchmarks where SGen-vs-Runtime delta matters.
|
||||
//new AcBinaryBenchmark(testData.Order, binaryFastModeNoSgenOption, "FastMode"),
|
||||
// NativeAOT-safe: AcSerializerCommon.Create*Getter/Setter falls back to reflection-based delegates
|
||||
// when RuntimeFeature.IsDynamicCodeSupported is false (slower but works under AOT publish).
|
||||
new AcBinaryBenchmark(testData.Order, binaryFastModeNoSgenOption, "FastMode"),
|
||||
// Default preset Byte[] — RefHandling=OnlyId (deduplicates IId-shared references on the wire) +
|
||||
// UseStringInterning=All (deduplicates repeated strings). Showcases the Default preset's wire-size
|
||||
// and CPU trade-off vs FastMode on the ~20% IId-ref / repeated-string test data.
|
||||
|
|
@ -602,7 +606,12 @@ public static class Program
|
|||
// ============================================================
|
||||
// MessagePack — for legacy comparison
|
||||
// ============================================================
|
||||
#if !AYCODE_NATIVEAOT
|
||||
// MessagePack v3's DynamicGenericResolver uses Activator.CreateInstance on trimmed
|
||||
// ListFormatter<T> et al. — fails under NativeAOT publish with "No parameterless constructor".
|
||||
// Excluded from the AOT build; available for regular JIT runs only.
|
||||
new MessagePackBenchmark(testData.Order, "ContractBased"),
|
||||
#endif
|
||||
|
||||
// System.Text.Json (commented — JSON serializer for reference; not in active suite)
|
||||
//new SystemTextJsonBenchmark(testData.Order, "Default")
|
||||
|
|
@ -1031,6 +1040,11 @@ public static class Program
|
|||
}
|
||||
}
|
||||
|
||||
#if !AYCODE_NATIVEAOT
|
||||
// MessagePack benchmark — excluded from NativeAOT build because v3's StandardResolver falls back
|
||||
// to DynamicGenericResolver for closed-generic types (List<TestOrderItem> et al.), which uses
|
||||
// Activator.CreateInstance on formatter types the AOT trimmer drops → MissingMethodException at runtime.
|
||||
// Available for regular JIT runs (`dotnet run`) only.
|
||||
private sealed class MessagePackBenchmark : ISerializerBenchmark
|
||||
{
|
||||
private readonly TestOrder _order;
|
||||
|
|
@ -1083,6 +1097,7 @@ public static class Program
|
|||
return DeepEqualsViaJson(_order, roundTripped);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Benchmarks AcBinary via the IBufferWriter overload with a pre-allocated, reused ArrayBufferWriter.
|
||||
|
|
|
|||
|
|
@ -465,6 +465,12 @@ public static class AcSerializerCommon
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Func<object, object?> CreateCompiledGetter(Type declaringType, PropertyInfo prop)
|
||||
{
|
||||
// NativeAOT (and other no-dynamic-code targets): fall back to plain reflection.
|
||||
// The returned delegate is cached per-property by the caller, so the indirection cost is paid
|
||||
// only at cache-population time — hot path is a direct delegate invocation either way.
|
||||
if (!RuntimeFeature.IsDynamicCodeSupported)
|
||||
return prop.GetValue;
|
||||
|
||||
var objParam = LExpression.Parameter(typeof(object), "obj");
|
||||
var castExpr = LExpression.Convert(objParam, declaringType);
|
||||
var propAccess = LExpression.Property(castExpr, prop);
|
||||
|
|
@ -478,6 +484,9 @@ public static class AcSerializerCommon
|
|||
/// </summary>
|
||||
public static Action<object, object?> CreateCompiledSetter(Type declaringType, PropertyInfo prop)
|
||||
{
|
||||
if (!RuntimeFeature.IsDynamicCodeSupported)
|
||||
return prop.SetValue;
|
||||
|
||||
var targetParam = LExpression.Parameter(typeof(object), "target");
|
||||
var valueParam = LExpression.Parameter(typeof(object), "value");
|
||||
|
||||
|
|
@ -524,6 +533,9 @@ public static class AcSerializerCommon
|
|||
var ctor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null);
|
||||
if (ctor == null) return null;
|
||||
|
||||
if (!RuntimeFeature.IsDynamicCodeSupported)
|
||||
return () => ctor.Invoke(null);
|
||||
|
||||
var newExpr = LExpression.New(ctor);
|
||||
var convert = LExpression.Convert(newExpr, typeof(object));
|
||||
return LExpression.Lambda<Func<object>>(convert).Compile();
|
||||
|
|
@ -534,15 +546,18 @@ public static class AcSerializerCommon
|
|||
/// </summary>
|
||||
public static Func<object, TProperty> CreateTypedGetter<TProperty>(Type declaringType, PropertyInfo prop)
|
||||
{
|
||||
if (!RuntimeFeature.IsDynamicCodeSupported)
|
||||
return obj => (TProperty)prop.GetValue(obj)!;
|
||||
|
||||
var objParam = LExpression.Parameter(typeof(object), "obj");
|
||||
var castExpr = LExpression.Convert(objParam, declaringType);
|
||||
var propAccess = LExpression.Property(castExpr, prop);
|
||||
|
||||
|
||||
// Only convert if property type differs from TProperty (avoids unnecessary boxing)
|
||||
Expression resultExpr = prop.PropertyType == typeof(TProperty)
|
||||
? propAccess
|
||||
: LExpression.Convert(propAccess, typeof(TProperty));
|
||||
|
||||
|
||||
return LExpression.Lambda<Func<object, TProperty>>(resultExpr, objParam).Compile();
|
||||
}
|
||||
|
||||
|
|
@ -551,6 +566,9 @@ public static class AcSerializerCommon
|
|||
/// </summary>
|
||||
public static Func<object, int> CreateEnumGetter(Type declaringType, PropertyInfo prop)
|
||||
{
|
||||
if (!RuntimeFeature.IsDynamicCodeSupported)
|
||||
return obj => Convert.ToInt32(prop.GetValue(obj));
|
||||
|
||||
var objParam = LExpression.Parameter(typeof(object), "obj");
|
||||
var castExpr = LExpression.Convert(objParam, declaringType);
|
||||
var propAccess = LExpression.Property(castExpr, prop);
|
||||
|
|
@ -563,6 +581,9 @@ public static class AcSerializerCommon
|
|||
/// </summary>
|
||||
public static Action<object, TProperty> CreateTypedSetter<TProperty>(Type declaringType, PropertyInfo prop)
|
||||
{
|
||||
if (!RuntimeFeature.IsDynamicCodeSupported)
|
||||
return (obj, value) => prop.SetValue(obj, value);
|
||||
|
||||
var objParam = LExpression.Parameter(typeof(object), "obj");
|
||||
var valueParam = LExpression.Parameter(typeof(TProperty), "value");
|
||||
var castExpr = LExpression.Convert(objParam, declaringType);
|
||||
|
|
@ -576,6 +597,12 @@ public static class AcSerializerCommon
|
|||
/// </summary>
|
||||
public static Action<object, int> CreateEnumSetter(Type declaringType, PropertyInfo prop)
|
||||
{
|
||||
if (!RuntimeFeature.IsDynamicCodeSupported)
|
||||
{
|
||||
var enumType = prop.PropertyType;
|
||||
return (obj, value) => prop.SetValue(obj, Enum.ToObject(enumType, value));
|
||||
}
|
||||
|
||||
var objParam = LExpression.Parameter(typeof(object), "obj");
|
||||
var valueParam = LExpression.Parameter(typeof(int), "value");
|
||||
var castExpr = LExpression.Convert(objParam, declaringType);
|
||||
|
|
|
|||
Loading…
Reference in New Issue