Refactor string interning to use flat int[] for perf
Replaces DupEntry[] with flat int[] for position-based string interning in AcBinaryDeserializer and AcBinarySerializer. Serializer now writes (position, cacheIndex) pairs as fixed int32s in bulk, and deserializer reads them with MemoryMarshal.Cast for ultra-fast, cache-friendly access. This eliminates per-pair parsing overhead and streamlines the hot path for string interning.
This commit is contained in:
parent
f313d5d9ea
commit
d0e2637741
|
|
@ -20,19 +20,11 @@ public static partial class AcBinaryDeserializer
|
|||
private List<string>? _propertyNames;
|
||||
private Dictionary<int, string>? _stringCache;
|
||||
|
||||
/// <summary>
|
||||
/// Footer entry for position-based string interning.
|
||||
/// </summary>
|
||||
private struct DupEntry
|
||||
{
|
||||
public int Position; // Stream position where string was first written
|
||||
public int CacheIndex; // Index in _internStringCache
|
||||
}
|
||||
|
||||
// Position-based string interning: 100% reliable cache matching
|
||||
private DupEntry[]? _dupEntries; // Footer: (position, cacheIndex) pairs sorted by position
|
||||
// Position-based string interning: flat int[] for cache-friendly access
|
||||
// Layout: [pos0, cacheIdx0, pos1, cacheIdx1, ...] - pairs sorted by position
|
||||
private int[]? _dupData; // Footer data: (position, cacheIndex) pairs as flat int array
|
||||
private string[]? _internStringCache; // Cache for duplicated strings only
|
||||
private int _dupCheckIndex; // Current position in _dupEntries
|
||||
private int _dupCheckIndex; // Current index in _dupData (increments by 2)
|
||||
private int _nextDupPosition; // Cached next dup position - avoids array access in hot path
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -85,7 +77,7 @@ public static partial class AcBinaryDeserializer
|
|||
_stringCache = null;
|
||||
|
||||
// Position-based string interning fields
|
||||
_dupEntries = null;
|
||||
_dupData = null;
|
||||
_internStringCache = null;
|
||||
_dupCheckIndex = 0;
|
||||
_nextDupPosition = int.MaxValue;
|
||||
|
|
@ -176,8 +168,8 @@ public static partial class AcBinaryDeserializer
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads string intern footer: [dupCount][(position, cacheIndex), ...]
|
||||
/// Position-based format for 100% reliable cache matching.
|
||||
/// Reads string intern footer: [dupCount][pos0][idx0][pos1][idx1]...
|
||||
/// Fixed int32 format for ultra-fast MemoryMarshal.Cast bulk read.
|
||||
/// </summary>
|
||||
private void ReadFooterStringIndices(int footerPosition)
|
||||
{
|
||||
|
|
@ -187,28 +179,28 @@ public static partial class AcBinaryDeserializer
|
|||
// Seek to footer
|
||||
_position = footerPosition;
|
||||
|
||||
// Read dup count and (position, cacheIndex) pairs
|
||||
// Read dup count (still VarUInt for backward compat header)
|
||||
var dupCount = (int)ReadVarUInt();
|
||||
if (dupCount == 0)
|
||||
{
|
||||
_dupEntries = Array.Empty<DupEntry>();
|
||||
_dupData = Array.Empty<int>();
|
||||
_internStringCache = Array.Empty<string>();
|
||||
_nextDupPosition = int.MaxValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
_dupEntries = new DupEntry[dupCount];
|
||||
for (var i = 0; i < dupCount; i++)
|
||||
{
|
||||
var position = (int)ReadVarUInt();
|
||||
var cacheIndex = (int)ReadVarUInt();
|
||||
_dupEntries[i] = new DupEntry { Position = position, CacheIndex = cacheIndex };
|
||||
}
|
||||
// Bulk read: dupCount * 2 int32s (position, cacheIndex pairs)
|
||||
var intCount = dupCount * 2;
|
||||
var byteCount = intCount * sizeof(int);
|
||||
EnsureAvailable(byteCount);
|
||||
|
||||
_dupData = new int[intCount];
|
||||
MemoryMarshal.Cast<byte, int>(_buffer.Slice(_position, byteCount)).CopyTo(_dupData);
|
||||
_position += byteCount;
|
||||
|
||||
// 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;
|
||||
_nextDupPosition = _dupData[0];
|
||||
}
|
||||
|
||||
// Seek back to data position
|
||||
|
|
@ -577,14 +569,15 @@ public static partial class AcBinaryDeserializer
|
|||
return;
|
||||
|
||||
// Match! Store in cache and advance to next dup position
|
||||
var entries = _dupEntries!;
|
||||
// _dupData layout: [pos0, cacheIdx0, pos1, cacheIdx1, ...]
|
||||
var data = _dupData!;
|
||||
var idx = _dupCheckIndex;
|
||||
_internStringCache![entries[idx].CacheIndex] = value;
|
||||
_internStringCache![data[idx + 1]] = value; // cacheIndex is at odd positions
|
||||
|
||||
idx++;
|
||||
idx += 2;
|
||||
_dupCheckIndex = idx;
|
||||
_nextDupPosition = idx < entries.Length
|
||||
? entries[idx].Position
|
||||
_nextDupPosition = idx < data.Length
|
||||
? data[idx] // next position is at even index
|
||||
: int.MaxValue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -237,7 +237,7 @@ public static partial class AcBinarySerializer
|
|||
|
||||
/// <summary>
|
||||
/// Writes the footer with (position, cacheIndex) pairs sorted by position.
|
||||
/// Position-based approach ensures 100% reliable cache matching in deserializer.
|
||||
/// Fixed int32 format for ultra-fast MemoryMarshal.Cast bulk read in deserializer.
|
||||
/// </summary>
|
||||
public void WriteInternedStringFooter()
|
||||
{
|
||||
|
|
@ -261,12 +261,17 @@ public static partial class AcBinarySerializer
|
|||
// Sort by StreamPosition (ascending) for deserializer sequential check
|
||||
entries.Sort((a, b) => a.Position.CompareTo(b.Position));
|
||||
|
||||
// Write pairs: (position, cacheIndex)
|
||||
// Write pairs as fixed int32s: [pos0][idx0][pos1][idx1]...
|
||||
// This allows MemoryMarshal.Cast bulk read in deserializer
|
||||
var byteCount = _nextCacheIndex * 2 * sizeof(int);
|
||||
EnsureCapacity(byteCount);
|
||||
var dest = MemoryMarshal.Cast<byte, int>(_buffer.AsSpan(_position, byteCount));
|
||||
for (var i = 0; i < _nextCacheIndex; i++)
|
||||
{
|
||||
WriteVarUInt((uint)entries[i].Position);
|
||||
WriteVarUInt((uint)entries[i].CacheIndex);
|
||||
dest[i * 2] = entries[i].Position;
|
||||
dest[i * 2 + 1] = entries[i].CacheIndex;
|
||||
}
|
||||
_position += byteCount;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
|
|||
Loading…
Reference in New Issue