Skip to content

Commit a0c3f1c

Browse files
perf(generator): optimize polymorphic deserialization with compile-time dispatch
Add optimal deserializers for polymorphic types in same-assembly scenarios: - Generate DeserializePolymorphic and DeserializeRefPolymorphic methods - Use switch-based dispatch on type IDs for known subtypes - Add optimalDeserializer and optimalDeserializerRef parameters - Support proper code indentation in generated methods - Eliminate runtime type lookups for same-assembly polymorphic types Mirrors serialization optimization from ee49724. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 3392ee3 commit a0c3f1c

File tree

6 files changed

+276
-61
lines changed

6 files changed

+276
-61
lines changed

src/Nino.Core/NinoDeserializer.cs

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ public static class CachedDeserializer<T>
145145
private static int _typeId = -1;
146146
private static DeserializeDelegate<T> _deserializer;
147147
private static DeserializeDelegateRef<T> _deserializerRef;
148+
private static DeserializeDelegate<T> _optimalDeserializer;
149+
private static DeserializeDelegateRef<T> _optimalDeserializerRef;
148150
private static readonly FastMap<int, DeserializeDelegate<T>> SubTypeDeserializers = new();
149151
private static readonly FastMap<int, DeserializeDelegateRef<T>> SubTypeDeserializerRefs = new();
150152
private static int _singleSubTypeId = int.MinValue;
@@ -167,11 +169,14 @@ public static class CachedDeserializer<T>
167169
internal static readonly bool IsSimpleType = !IsReferenceOrContainsReferences && !HasBaseType;
168170

169171
public static void SetDeserializer(int typeId, DeserializeDelegate<T> deserializer,
170-
DeserializeDelegateRef<T> deserializerRef)
172+
DeserializeDelegateRef<T> deserializerRef, DeserializeDelegate<T> optimalDeserializer,
173+
DeserializeDelegateRef<T> optimalDeserializerRef)
171174
{
172175
_typeId = typeId;
173176
_deserializer = deserializer;
174177
_deserializerRef = deserializerRef;
178+
_optimalDeserializer = optimalDeserializer;
179+
_optimalDeserializerRef = optimalDeserializerRef;
175180
SubTypeDeserializers.Add(typeId, _deserializer);
176181
SubTypeDeserializerRefs.Add(typeId, _deserializerRef);
177182
_singleSubTypeId = int.MinValue;
@@ -278,18 +283,15 @@ public static void Deserialize(out T value, ref Reader reader)
278283
return;
279284
}
280285

281-
// FAST PATH 2: JIT-eliminated branch for sealed types
282-
// If T is sealed or a value type, it CANNOT have a different runtime type
283-
// This completely eliminates polymorphic deserialization overhead
284-
if (IsSealed || SubTypeDeserializers.Count == 1)
285-
{
286-
// DIRECT DELEGATE: Generated code path - no polymorphism possible
287-
_deserializer(out value, ref reader);
288-
}
289-
else
286+
// FAST PATH 1: Optimal serializer for polymorphic usage (with subtypes)
287+
// This is a pre-generated serializer that handles polymorphism internally
288+
if (_optimalDeserializer != null)
290289
{
291-
DeserializePolymorphic(out value, ref reader);
290+
_optimalDeserializer(out value, ref reader);
291+
return;
292292
}
293+
294+
DeserializePolymorphic(out value, ref reader);
293295
}
294296

295297
// ULTRA-OPTIMIZED: Single core ref method with all paths optimized
@@ -303,23 +305,30 @@ public static void DeserializeRef(ref T value, ref Reader reader)
303305
return;
304306
}
305307

308+
// FAST PATH 1: Optimal serializer for polymorphic usage (with subtypes)
309+
// This is a pre-generated serializer that handles polymorphism internally
310+
if (_optimalDeserializerRef != null)
311+
{
312+
_optimalDeserializerRef(ref value, ref reader);
313+
return;
314+
}
315+
316+
DeserializeRefPolymorphic(ref value, ref reader);
317+
}
318+
319+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
320+
public static void DeserializePolymorphic(out T value, ref Reader reader)
321+
{
306322
// FAST PATH 2: JIT-eliminated branch for sealed types
307323
// If T is sealed or a value type, it CANNOT have a different runtime type
308324
// This completely eliminates polymorphic deserialization overhead
309-
if (IsSealed || SubTypeDeserializerRefs.Count == 1)
325+
if (IsSealed || SubTypeDeserializers.Count == 1)
310326
{
311327
// DIRECT DELEGATE: Generated code path - no polymorphism possible
312-
_deserializerRef(ref value, ref reader);
313-
}
314-
else
315-
{
316-
DeserializeRefPolymorphic(ref value, ref reader);
328+
_deserializer(out value, ref reader);
329+
return;
317330
}
318-
}
319331

320-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
321-
private static void DeserializePolymorphic(out T value, ref Reader reader)
322-
{
323332
// Peek type info for polymorphic types
324333
reader.Peak(out int typeId);
325334

@@ -364,8 +373,18 @@ private static void DeserializePolymorphic(out T value, ref Reader reader)
364373
}
365374

366375
[MethodImpl(MethodImplOptions.AggressiveInlining)]
367-
private static void DeserializeRefPolymorphic(ref T value, ref Reader reader)
376+
public static void DeserializeRefPolymorphic(ref T value, ref Reader reader)
368377
{
378+
// FAST PATH 2: JIT-eliminated branch for sealed types
379+
// If T is sealed or a value type, it CANNOT have a different runtime type
380+
// This completely eliminates polymorphic deserialization overhead
381+
if (IsSealed || SubTypeDeserializerRefs.Count == 1)
382+
{
383+
// DIRECT DELEGATE: Generated code path - no polymorphism possible
384+
_deserializerRef(ref value, ref reader);
385+
return;
386+
}
387+
369388
// Read type info first for polymorphic types
370389
reader.Peak(out int typeId);
371390

src/Nino.Core/NinoTypeMetadata.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,10 @@ private static class SerializerRegistration<T>
5858
[EditorBrowsable(EditorBrowsableState.Never)]
5959
public static void RegisterDeserializer<T>(int typeId,
6060
DeserializeDelegate<T> deserializer,
61-
DeserializeDelegateRef<T> deserializerRef, bool hasBaseType)
61+
DeserializeDelegateRef<T> deserializerRef,
62+
DeserializeDelegate<T> optimalDeserializer,
63+
DeserializeDelegateRef<T> optimalDeserializerRef,
64+
bool hasBaseType)
6265
{
6366
lock (DeserializerRegistration<T>.Lock)
6467
{
@@ -73,7 +76,7 @@ public static void RegisterDeserializer<T>(int typeId,
7376
HasBaseTypeMap.Add(typeHandle, true);
7477
}
7578

76-
CachedDeserializer<T>.SetDeserializer(typeId, deserializer, deserializerRef);
79+
CachedDeserializer<T>.SetDeserializer(typeId, deserializer, deserializerRef, optimalDeserializer, optimalDeserializerRef);
7780
(DeserializeDelegateBoxed outOverload,
7881
DeserializeDelegateRefBoxed refOverload) pair = (CachedDeserializer<T>.DeserializeBoxed,
7982
CachedDeserializer<T>.DeserializeBoxed);

src/Nino.Generator/BuiltInType/NinoBuiltInTypesGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ protected override void Generate(SourceProductionContext spc)
111111
registrationCode.AppendLine(
112112
$" NinoTypeMetadata.RegisterSerializer<{typeName}>(Serializer.Serialize, Serializer.Serialize, false);");
113113
registrationCode.AppendLine(
114-
$" NinoTypeMetadata.RegisterDeserializer<{typeName}>(-1, Deserializer.Deserialize, Deserializer.DeserializeRef, false);");
114+
$" NinoTypeMetadata.RegisterDeserializer<{typeName}>(-1, Deserializer.Deserialize, Deserializer.DeserializeRef, Deserializer.Deserialize, Deserializer.DeserializeRef, false);");
115115
}
116116

117117
var curNamespace = Compilation.AssemblyName!.GetNamespace();

0 commit comments

Comments
 (0)