Refactor deserialization property cache construction

Move CacheMap building to dedicated method for efficiency.
Remove incremental cache logic and SourceHashes field.
Simplify property population and update documentation.
Improves performance and code clarity.
This commit is contained in:
Loretta 2026-02-05 07:12:08 +01:00
parent cd3d65b5f4
commit 097c1e8efe
3 changed files with 39 additions and 65 deletions

View File

@ -57,10 +57,8 @@ public static partial class AcBinaryDeserializer
/// <summary> /// <summary>
/// Unified property populate for both UseMetadata and non-UseMetadata modes. /// Unified property populate for both UseMetadata and non-UseMetadata modes.
/// UseMetadata=false: properties[i] gives the setter directly (same-type, index-based). /// UseMetadata=true: cacheMap[i] gives the setter (null → skip).
/// UseMetadata=true: cacheMap[i] gives the setter. If null (first object of this type), /// UseMetadata=false: properties[i] gives the setter directly.
/// fast path checks props[index+1], fallback searches from props[index+2].
/// index tracks last matched target property index (starts at -1).
/// </summary> /// </summary>
private static void PopulateObjectPropertiesIndexed( private static void PopulateObjectPropertiesIndexed(
ref BinaryDeserializationContext context, ref BinaryDeserializationContext context,
@ -78,51 +76,10 @@ public static partial class AcBinaryDeserializer
// UseMetadata: cacheMap.Length a source property-k száma // UseMetadata: cacheMap.Length a source property-k száma
// Non-UseMetadata: properties.Length a target property-k száma (source == target) // Non-UseMetadata: properties.Length a target property-k száma (source == target)
var propCount = cacheMap?.Length ?? properties.Length; var propCount = cacheMap?.Length ?? properties.Length;
var sourceHashes = wrapper.SourceHashes; // only non-null when cacheMap != null
var index = -1; // last matched target property index (for incremental CacheMap building)
for (int i = 0; i < propCount; i++) for (int i = 0; i < propCount; i++)
{ {
BinaryPropertySetterBase? propInfo; var propInfo = cacheMap != null ? cacheMap[i] : properties[i];
if (cacheMap != null)
{
// UseMetadata mode
propInfo = cacheMap[i];
if (propInfo == null && sourceHashes != null)
{
// First object of this type — inkrementális CacheMap felépítés
var hash = sourceHashes[i];
// Fast path: props[index+1] (same-type → next property in order)
var nextIdx = index + 1;
if (nextIdx < properties.Length && properties[nextIdx].PropertyNameHash == hash)
{
propInfo = properties[nextIdx];
index = nextIdx;
}
else
{
// Fallback: search from index+2 (cross-type, alphabetical order → forward only)
for (var j = nextIdx + 1; j < properties.Length; j++)
{
if (properties[j].PropertyNameHash == hash)
{
propInfo = properties[j];
index = j;
break;
}
}
}
cacheMap[i] = propInfo; // null marad ha nincs match → skip next time too
}
}
else
{
// Non-UseMetadata: direct index-based
propInfo = properties[i];
}
var peekCode = context.PeekByte(); var peekCode = context.PeekByte();
@ -224,10 +181,6 @@ public static partial class AcBinaryDeserializer
ex); ex);
} }
} }
// CacheMap kész — sourceHashes null-ra, hogy a következő objektumnál ne keressen újra
if (sourceHashes != null)
wrapper.SourceHashes = null;
} }
/// <summary> /// <summary>

View File

@ -1058,12 +1058,9 @@ public static partial class AcBinaryDeserializer
context.RegisterInternedValue(instance, streamPosition); context.RegisterInternedValue(instance, streamPosition);
} }
// CacheMap + SourceHashes a wrapper-en (per-context, futásonként változhat) // CacheMap felépítése ha még nincs (1x per target type × source type kombináció)
if (wrapper.CacheMap == null) if (wrapper.CacheMap == null)
{ BuildCacheMap(wrapper, sourceHashes);
wrapper.CacheMap = new BinaryPropertySetterBase?[sourceHashes.Length];
wrapper.SourceHashes = sourceHashes;
}
PopulateObject(ref context, instance, wrapper, depth, skipDefaultWrite: true); PopulateObject(ref context, instance, wrapper, depth, skipDefaultWrite: true);
@ -1108,10 +1105,41 @@ public static partial class AcBinaryDeserializer
var wrapper = context.ContextClass.GetWrapper(targetType); var wrapper = context.ContextClass.GetWrapper(targetType);
if (wrapper.CacheMap == null) if (wrapper.CacheMap == null)
BuildCacheMap(wrapper, sourceHashes);
}
/// <summary>
/// CacheMap felépítése: source property hash-ek → target PropertySetter mapping.
/// Fast path: same-type esetén props[index+1] egyezik. Cross-type: keresés index+1-től.
/// </summary>
private static void BuildCacheMap(TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper, int[] sourceHashes)
{
var properties = wrapper.Metadata.PropertiesArray;
var cacheMap = new BinaryPropertySetterBase?[sourceHashes.Length];
var index = -1;
for (var i = 0; i < sourceHashes.Length; i++)
{ {
wrapper.CacheMap = new BinaryPropertySetterBase?[sourceHashes.Length]; var hash = sourceHashes[i];
wrapper.SourceHashes = sourceHashes; var nextIdx = index + 1;
if (nextIdx < properties.Length && properties[nextIdx].PropertyNameHash == hash)
{
cacheMap[i] = properties[nextIdx];
index = nextIdx;
}
else
{
for (var j = nextIdx; j < properties.Length; j++)
{
if (properties[j].PropertyNameHash == hash)
{
cacheMap[i] = properties[j];
index = j;
break;
}
}
}
} }
wrapper.CacheMap = cacheMap;
} }
#endregion #endregion

View File

@ -46,16 +46,10 @@ public sealed class TypeMetadataWrapper<TMetadata> where TMetadata : TypeMetadat
/// <summary> /// <summary>
/// UseMetadata cachemap: source property index → target PropertySetter. /// UseMetadata cachemap: source property index → target PropertySetter.
/// Per-context (wrapper-szintű), mert futásonként eltérő source type-pal találkozhat. /// Per-context (wrapper-szintű), mert futásonként eltérő source type-pal találkozhat.
/// Inkrementálisan épül a PopulateObjectPropertiesIndexed-ben. /// Felépül a ReadObjectWithMetadata-ban, első előforduláskor.
/// </summary> /// </summary>
internal BinaryPropertySetterBase?[]? CacheMap; internal BinaryPropertySetterBase?[]? CacheMap;
/// <summary>
/// Source property hash-ek a CacheMap-hez — az inline metadata-ból olvasva.
/// Per-context, mert eltérő source type-pal más hash-ek jönnek.
/// </summary>
internal int[]? SourceHashes;
#region Typed IdentityMaps - No generic type checks in hot path! #region Typed IdentityMaps - No generic type checks in hot path!
/// <summary> /// <summary>
@ -133,7 +127,6 @@ public sealed class TypeMetadataWrapper<TMetadata> where TMetadata : TypeMetadat
{ {
MetadataFooterIndex = -1; MetadataFooterIndex = -1;
CacheMap = null; CacheMap = null;
SourceHashes = null;
if (SmallIdBitmap != null) if (SmallIdBitmap != null)
Array.Clear(SmallIdBitmap); Array.Clear(SmallIdBitmap);