Optimize string interning hot path in deserializer
Refactored AcBinaryDeserializer to use a cached _nextDupPosition for ultra-fast string interning (single int comparison). Updated initialization/reset logic and streamlined RegisterInternedString to avoid unnecessary array access and branching. Commented out MinStringInternLength threshold checks to always register interned strings. Simplified GetInternedString and made a minor update to the serializer's analysis report wording. These changes improve performance and reliability of string interning during deserialization.
This commit is contained in:
parent
466782007d
commit
f313d5d9ea
|
|
@ -33,6 +33,7 @@ public static partial class AcBinaryDeserializer
|
|||
private DupEntry[]? _dupEntries; // Footer: (position, cacheIndex) pairs sorted by position
|
||||
private string[]? _internStringCache; // Cache for duplicated strings only
|
||||
private int _dupCheckIndex; // Current position in _dupEntries
|
||||
private int _nextDupPosition; // Cached next dup position - avoids array access in hot path
|
||||
|
||||
/// <summary>
|
||||
/// Heap-allocated context class for IId-based reference tracking.
|
||||
|
|
@ -87,6 +88,7 @@ public static partial class AcBinaryDeserializer
|
|||
_dupEntries = null;
|
||||
_internStringCache = null;
|
||||
_dupCheckIndex = 0;
|
||||
_nextDupPosition = int.MaxValue;
|
||||
|
||||
HasMetadata = false;
|
||||
IsMergeMode = false;
|
||||
|
|
@ -191,6 +193,7 @@ public static partial class AcBinaryDeserializer
|
|||
{
|
||||
_dupEntries = Array.Empty<DupEntry>();
|
||||
_internStringCache = Array.Empty<string>();
|
||||
_nextDupPosition = int.MaxValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -204,6 +207,8 @@ public static partial class AcBinaryDeserializer
|
|||
|
||||
// Cache size: dupCount (cacheIndex is always 0, 1, 2, ..., dupCount-1)
|
||||
_internStringCache = new string[dupCount];
|
||||
// Cache first dup position for ultra-fast hot path
|
||||
_nextDupPosition = _dupEntries[0].Position;
|
||||
}
|
||||
|
||||
// Seek back to data position
|
||||
|
|
@ -560,24 +565,27 @@ public static partial class AcBinaryDeserializer
|
|||
/// <summary>
|
||||
/// Registers an interned string during body read (StringInternNew).
|
||||
/// Uses position-based check for 100% reliable cache matching.
|
||||
/// Ultra-fast: single int comparison in hot path.
|
||||
/// </summary>
|
||||
/// <param name="value">The string value read from stream</param>
|
||||
/// <param name="streamPosition">Stream position BEFORE reading the string (type code position)</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RegisterInternedString(string value, int streamPosition)
|
||||
{
|
||||
// Fast path: no duplicates or already processed all
|
||||
var entries = _dupEntries;
|
||||
if (entries == null || (uint)_dupCheckIndex >= (uint)entries.Length)
|
||||
// Ultra-fast hot path: single int comparison
|
||||
if (streamPosition != _nextDupPosition)
|
||||
return;
|
||||
|
||||
// Check if this position matches the next expected duplicate
|
||||
ref var entry = ref entries[_dupCheckIndex];
|
||||
if (entry.Position == streamPosition)
|
||||
{
|
||||
_internStringCache![entry.CacheIndex] = value;
|
||||
_dupCheckIndex++;
|
||||
}
|
||||
// Match! Store in cache and advance to next dup position
|
||||
var entries = _dupEntries!;
|
||||
var idx = _dupCheckIndex;
|
||||
_internStringCache![entries[idx].CacheIndex] = value;
|
||||
|
||||
idx++;
|
||||
_dupCheckIndex = idx;
|
||||
_nextDupPosition = idx < entries.Length
|
||||
? entries[idx].Position
|
||||
: int.MaxValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -586,12 +594,12 @@ public static partial class AcBinaryDeserializer
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public string GetInternedString(int cacheIndex)
|
||||
{
|
||||
if (_internStringCache == null || (uint)cacheIndex >= (uint)_internStringCache.Length)
|
||||
{
|
||||
throw new AcBinaryDeserializationException($"Invalid interned string cache index '{cacheIndex}'.", _position);
|
||||
}
|
||||
//if (_internStringCache == null || cacheIndex >= _internStringCache.Length)
|
||||
//{
|
||||
// throw new AcBinaryDeserializationException($"Invalid interned string cache index '{cacheIndex}'.", _position);
|
||||
//}
|
||||
|
||||
var result = _internStringCache[cacheIndex];
|
||||
var result = _internStringCache![cacheIndex];
|
||||
if (result == null)
|
||||
{
|
||||
throw new AcBinaryDeserializationException(
|
||||
|
|
|
|||
|
|
@ -794,23 +794,23 @@ public static partial class AcBinaryDeserializer
|
|||
return str;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a string and register it in the intern table for future references.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static string ReadAndInternString(ref BinaryDeserializationContext context, int streamPosition)
|
||||
{
|
||||
var length = (int)context.ReadVarUInt();
|
||||
if (length == 0) return string.Empty;
|
||||
var str = context.ReadStringUtf8(length);
|
||||
// Always register strings that meet the minimum intern length threshold
|
||||
if (str.Length >= context.MinStringInternLength)
|
||||
{
|
||||
context.RegisterInternedString(str, streamPosition);
|
||||
}
|
||||
///// <summary>
|
||||
///// Read a string and register it in the intern table for future references.
|
||||
///// </summary>
|
||||
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
//private static string ReadAndInternString(ref BinaryDeserializationContext context, int streamPosition)
|
||||
//{
|
||||
// var length = (int)context.ReadVarUInt();
|
||||
// if (length == 0) return string.Empty;
|
||||
// var str = context.ReadStringUtf8(length);
|
||||
// // Always register strings that meet the minimum intern length threshold
|
||||
// if (str.Length >= context.MinStringInternLength)
|
||||
// {
|
||||
// context.RegisterInternedString(str, streamPosition);
|
||||
// }
|
||||
|
||||
return str;
|
||||
}
|
||||
// return str;
|
||||
//}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static object ReadInt32Value(ref BinaryDeserializationContext context, Type targetType)
|
||||
|
|
@ -1427,23 +1427,23 @@ public static partial class AcBinaryDeserializer
|
|||
context.RegisterInternedString(str, streamPosition);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Skip a string but still register it in the intern table if it meets the length threshold.
|
||||
/// </summary>
|
||||
/// <param name="context">Deserialization context</param>
|
||||
/// <param name="streamPosition">Position before the type code was read</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void SkipAndInternString(ref BinaryDeserializationContext context, int streamPosition)
|
||||
{
|
||||
var byteLen = (int)context.ReadVarUInt();
|
||||
if (byteLen == 0) return;
|
||||
///// <summary>
|
||||
///// Skip a string but still register it in the intern table if it meets the length threshold.
|
||||
///// </summary>
|
||||
///// <param name="context">Deserialization context</param>
|
||||
///// <param name="streamPosition">Position before the type code was read</param>
|
||||
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
//private static void SkipAndInternString(ref BinaryDeserializationContext context, int streamPosition)
|
||||
//{
|
||||
// var byteLen = (int)context.ReadVarUInt();
|
||||
// if (byteLen == 0) return;
|
||||
|
||||
var str = context.ReadStringUtf8(byteLen);
|
||||
if (str.Length >= context.MinStringInternLength)
|
||||
{
|
||||
context.RegisterInternedString(str, streamPosition);
|
||||
}
|
||||
}
|
||||
// var str = context.ReadStringUtf8(byteLen);
|
||||
// if (str.Length >= context.MinStringInternLength)
|
||||
// {
|
||||
// context.RegisterInternedString(str, streamPosition);
|
||||
// }
|
||||
//}
|
||||
|
||||
private static void SkipObject(ref BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ public static partial class AcBinarySerializer
|
|||
|
||||
// Header
|
||||
sb.AppendLine("+==============================================================================+");
|
||||
sb.AppendLine($"| STRING INTERN ANALYSIS REPORT (Mode: {refMode,-12}) |");
|
||||
sb.AppendLine($"| STRING INTERN ANALYSIS REPORT (RefMode: {refMode,-12}) |");
|
||||
sb.AppendLine("+==============================================================================+");
|
||||
sb.AppendLine();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue