Skip to content

Commit 722ca4b

Browse files
perf(core): remove inline cache from polymorphic serialization
Remove inline cache layer for polymorphic type handling: **Serialization:** - Remove _cachedTypeHandle and _cachedSerializer inline cache - Remove volatile cache reads/writes - Simplify to direct FastMap lookup after base type check - Reduces memory overhead and eliminates cache maintenance cost **Deserialization:** - Remove _cachedTypeIdOut/_cachedTypeIdRef and cached deserializers - Remove volatile cache reads/writes - Simplify to direct FastMap lookup after exact type match check - Consistent with serialization path **Rationale:** - FastMap already provides O(1) lookup performance - Inline cache added complexity and volatile overhead - Most polymorphic usage has limited subtype variance - FastMap lookup is fast enough for typical use cases - Simpler code is easier to maintain and optimize This simplification maintains performance while reducing code complexity and eliminating the memory/synchronization overhead of the inline cache. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 6bbaeea commit 722ca4b

2 files changed

Lines changed: 2 additions & 45 deletions

File tree

src/Nino.Core/NinoDeserializer.cs

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -164,11 +164,6 @@ public static class CachedDeserializer<T>
164164
private static readonly FastMap<int, DeserializeDelegateRef<T>> SubTypeDeserializerRefs = new();
165165

166166
// Inline cache for polymorphic deserialization (separate caches for out/ref)
167-
private static int _cachedTypeIdOut = -1;
168-
private static DeserializeDelegate<T> _cachedDeserializer;
169-
private static int _cachedTypeIdRef = -1;
170-
private static DeserializeDelegateRef<T> _cachedDeserializerRef;
171-
172167
// Cache expensive type checks
173168
private static readonly bool IsReferenceOrContainsReferences =
174169
RuntimeHelpers.IsReferenceOrContainsReferences<T>();
@@ -335,21 +330,10 @@ private static void DeserializePolymorphic(out T value, ref Reader reader)
335330
return;
336331
}
337332

338-
// Fast path: inline cache hit (most common case for batched data)
339-
int cachedId = Volatile.Read(ref _cachedTypeIdOut);
340-
var cachedDeserializer = Volatile.Read(ref _cachedDeserializer);
341-
if (typeId == cachedId && cachedDeserializer != null)
342-
{
343-
cachedDeserializer(out value, ref reader);
344-
return;
345-
}
346-
347333
// Slow path: lookup and update cache
348334
// Handle subtype with single lookup
349335
if (SubTypeDeserializers.TryGetValue(typeId, out var subTypeDeserializer))
350336
{
351-
Volatile.Write(ref _cachedTypeIdOut, typeId);
352-
Volatile.Write(ref _cachedDeserializer, subTypeDeserializer);
353337
subTypeDeserializer(out value, ref reader);
354338
return;
355339
}
@@ -377,21 +361,10 @@ private static void DeserializeRefPolymorphic(ref T value, ref Reader reader)
377361
return;
378362
}
379363

380-
// Fast path: inline cache hit (most common case for batched data)
381-
int cachedId = Volatile.Read(ref _cachedTypeIdRef);
382-
var cachedDeserializer = Volatile.Read(ref _cachedDeserializerRef);
383-
if (typeId == cachedId && cachedDeserializer != null)
384-
{
385-
cachedDeserializer(ref value, ref reader);
386-
return;
387-
}
388-
389364
// Slow path: lookup and update cache
390365
// Handle subtype deserialization
391366
if (SubTypeDeserializerRefs.TryGetValue(typeId, out var subTypeDeserializer))
392367
{
393-
Volatile.Write(ref _cachedTypeIdRef, typeId);
394-
Volatile.Write(ref _cachedDeserializerRef, subTypeDeserializer);
395368
subTypeDeserializer(ref value, ref reader);
396369
return;
397370
}

src/Nino.Core/NinoSerializer.cs

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,6 @@ public static class CachedSerializer<T>
183183
public static SerializeDelegate<T> Serializer;
184184
public static readonly FastMap<IntPtr, SerializeDelegate<T>> SubTypeSerializers = new();
185185

186-
// Inline cache for polymorphic serialization
187-
private static IntPtr _cachedTypeHandle;
188-
private static SerializeDelegate<T> _cachedSerializer;
189-
190186
// Cache expensive type checks
191187
internal static readonly bool IsReferenceOrContainsReferences =
192188
RuntimeHelpers.IsReferenceOrContainsReferences<T>();
@@ -288,29 +284,17 @@ private static unsafe void SerializePolymorphic(T val, ref Writer writer)
288284
IntPtr actualTypeHandle = val.GetType().TypeHandle.Value;
289285
#endif
290286

291-
// FAST PATH 1: Base type (common for non-polymorphic usage)
287+
// FAST PATH: Base type (common for non-polymorphic usage)
292288
if (actualTypeHandle == TypeHandle)
293289
{
294290
Serializer(val, ref writer);
295291
return;
296292
}
297-
298-
// FAST PATH 2: Inline cache hit (most common for homogeneous batches)
299-
// Check this FIRST - single pointer comparison, very cheap
300-
var cachedHandle = Volatile.Read(ref _cachedTypeHandle);
301-
var cachedSerializer = Volatile.Read(ref _cachedSerializer);
302-
if (actualTypeHandle == cachedHandle && cachedSerializer != null)
303-
{
304-
cachedSerializer(val, ref writer);
305-
return;
306-
}
307293

308294
// SLOW PATH: Full lookup in subtype map
309295
// Handle subtype serialization
310296
if (SubTypeSerializers.TryGetValue(actualTypeHandle, out var subTypeSerializer))
311297
{
312-
Volatile.Write(ref _cachedTypeHandle, actualTypeHandle);
313-
Volatile.Write(ref _cachedSerializer, subTypeSerializer);
314298
subTypeSerializer(val, ref writer);
315299
return;
316300
}
@@ -319,4 +303,4 @@ private static unsafe void SerializePolymorphic(T val, ref Writer writer)
319303
}
320304
}
321305
#pragma warning restore CA1000
322-
}
306+
}

0 commit comments

Comments
 (0)