Enable property-level string interning via attribute
Introduce [AcStringIntern] for selective string interning on properties. Update serializer to use a per-property eligibility flag, improving efficiency and control over which string fields participate in interning during binary serialization. Update test models and internal context accordingly.
This commit is contained in:
parent
6f88306e54
commit
e30efff56c
|
|
@ -1,4 +1,5 @@
|
||||||
using AyCode.Core.Interfaces;
|
using AyCode.Core.Interfaces;
|
||||||
|
using AyCode.Core.Serializers.Binaries;
|
||||||
|
|
||||||
namespace AyCode.Core.Tests.TestModels;
|
namespace AyCode.Core.Tests.TestModels;
|
||||||
|
|
||||||
|
|
@ -30,7 +31,9 @@ public static class AcSerializerModels
|
||||||
|
|
||||||
public class TestClassWithRepeatedStrings
|
public class TestClassWithRepeatedStrings
|
||||||
{
|
{
|
||||||
|
[AcStringIntern(true)]
|
||||||
public string Field1 { get; set; } = "";
|
public string Field1 { get; set; } = "";
|
||||||
|
[AcStringIntern(true)]
|
||||||
public string Field2 { get; set; } = "";
|
public string Field2 { get; set; } = "";
|
||||||
public string Field3 { get; set; } = "";
|
public string Field3 { get; set; } = "";
|
||||||
public string Field4 { get; set; } = "";
|
public string Field4 { get; set; } = "";
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using AyCode.Core.Extensions;
|
using AyCode.Core.Extensions;
|
||||||
using AyCode.Core.Interfaces;
|
using AyCode.Core.Interfaces;
|
||||||
using AyCode.Core.Serializers.Attributes;
|
using AyCode.Core.Serializers.Attributes;
|
||||||
|
using AyCode.Core.Serializers.Binaries;
|
||||||
using AyCode.Core.Serializers.Jsons;
|
using AyCode.Core.Serializers.Jsons;
|
||||||
using MessagePack;
|
using MessagePack;
|
||||||
using MongoDB.Bson.Serialization.Attributes;
|
using MongoDB.Bson.Serialization.Attributes;
|
||||||
|
|
@ -60,6 +61,7 @@ public class SharedTag : IId<int>
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
[Key(1)]
|
[Key(1)]
|
||||||
public string Name { get; set; } = "";
|
public string Name { get; set; } = "";
|
||||||
|
[AcStringIntern(true)]
|
||||||
[Key(2)]
|
[Key(2)]
|
||||||
public string Color { get; set; } = "#000000";
|
public string Color { get; set; } = "#000000";
|
||||||
[Key(3)]
|
[Key(3)]
|
||||||
|
|
@ -133,12 +135,15 @@ public class SharedUser : IId<int>
|
||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
public class UserPreferences
|
public class UserPreferences
|
||||||
{
|
{
|
||||||
|
[AcStringIntern(true)]
|
||||||
[Key(0)]
|
[Key(0)]
|
||||||
public string Theme { get; set; } = "light";
|
public string Theme { get; set; } = "light";
|
||||||
|
[AcStringIntern(true)]
|
||||||
[Key(1)]
|
[Key(1)]
|
||||||
public string Language { get; set; } = "en-US";
|
public string Language { get; set; } = "en-US";
|
||||||
[Key(2)]
|
[Key(2)]
|
||||||
public bool NotificationsEnabled { get; set; } = true;
|
public bool NotificationsEnabled { get; set; } = true;
|
||||||
|
[AcStringIntern(true)]
|
||||||
[Key(3)]
|
[Key(3)]
|
||||||
public string? EmailDigestFrequency { get; set; }
|
public string? EmailDigestFrequency { get; set; }
|
||||||
}
|
}
|
||||||
|
|
@ -155,8 +160,10 @@ public class UserPreferences
|
||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
public class MetadataInfo
|
public class MetadataInfo
|
||||||
{
|
{
|
||||||
|
[AcStringIntern(true)]
|
||||||
[Key(0)]
|
[Key(0)]
|
||||||
public string Key { get; set; } = "";
|
public string Key { get; set; } = "";
|
||||||
|
[AcStringIntern(true)]
|
||||||
[Key(1)]
|
[Key(1)]
|
||||||
public string Value { get; set; } = "";
|
public string Value { get; set; } = "";
|
||||||
[Key(2)]
|
[Key(2)]
|
||||||
|
|
@ -240,6 +247,7 @@ public class TestOrderItem : IId<int>
|
||||||
{
|
{
|
||||||
[Key(0)]
|
[Key(0)]
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
[AcStringIntern(true)]
|
||||||
[Key(1)]
|
[Key(1)]
|
||||||
public string ProductName { get; set; } = "";
|
public string ProductName { get; set; } = "";
|
||||||
[Key(2)]
|
[Key(2)]
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,13 @@ public static partial class AcBinarySerializer
|
||||||
/// <summary>Write pass visit counter. Mirrors ScanVisitIndex ordering.</summary>
|
/// <summary>Write pass visit counter. Mirrors ScanVisitIndex ordering.</summary>
|
||||||
internal int WriteVisitIndex;
|
internal int WriteVisitIndex;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set per-property in WritePropertyOrSkip before calling WriteString.
|
||||||
|
/// Controls whether the current string property participates in the cursor-based interning.
|
||||||
|
/// Must mirror scan pass's prop.UseStringPropertyInterning() check.
|
||||||
|
/// </summary>
|
||||||
|
internal bool StringInternEligible;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to consume the next write plan entry at the current WriteVisitIndex.
|
/// Tries to consume the next write plan entry at the current WriteVisitIndex.
|
||||||
/// Returns true if the entry matches (duplicate exists at this visit point).
|
/// Returns true if the entry matches (duplicate exists at this visit point).
|
||||||
|
|
@ -264,6 +271,7 @@ public static partial class AcBinarySerializer
|
||||||
ScanVisitIndex = 0;
|
ScanVisitIndex = 0;
|
||||||
WritePlanCursor = 0;
|
WritePlanCursor = 0;
|
||||||
WriteVisitIndex = 0;
|
WriteVisitIndex = 0;
|
||||||
|
StringInternEligible = false;
|
||||||
|
|
||||||
// Clear write plan string references to avoid GC pinning, keep array if small enough
|
// Clear write plan string references to avoid GC pinning, keep array if small enough
|
||||||
if (_writePlan != null)
|
if (_writePlan != null)
|
||||||
|
|
|
||||||
|
|
@ -912,8 +912,10 @@ public static partial class AcBinarySerializer
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.UseStringInterning && context.IsValidForInterningString(value.Length))
|
if (context.StringInternEligible && context.IsValidForInterningString(value.Length))
|
||||||
{
|
{
|
||||||
|
context.StringInternEligible = false;
|
||||||
|
|
||||||
if (context.TryConsumeWritePlanEntry(out var planEntry))
|
if (context.TryConsumeWritePlanEntry(out var planEntry))
|
||||||
{
|
{
|
||||||
ValidateWritePlanString(in planEntry, value);
|
ValidateWritePlanString(in planEntry, value);
|
||||||
|
|
@ -1383,6 +1385,7 @@ public static partial class AcBinarySerializer
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
context.StringInternEligible = prop.UseStringPropertyInterning(context.Options.UseStringInterning);
|
||||||
WriteString(value, context);
|
WriteString(value, context);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
@ -1391,6 +1394,8 @@ public static partial class AcBinarySerializer
|
||||||
{
|
{
|
||||||
// Object type (collection, complex object, byte[], dictionary)
|
// Object type (collection, complex object, byte[], dictionary)
|
||||||
// Use pre-cached wrapper, fallback to GetWrapper on miss/polymorphism
|
// Use pre-cached wrapper, fallback to GetWrapper on miss/polymorphism
|
||||||
|
// Set interning eligibility for string collection elements
|
||||||
|
context.StringInternEligible = prop.IsStringCollectionProperty && prop.UseStringPropertyInterning(context.Options.UseStringInterning);
|
||||||
var value = prop.GetValue(obj);
|
var value = prop.GetValue(obj);
|
||||||
|
|
||||||
// SKIP marker only for null (reference types)
|
// SKIP marker only for null (reference types)
|
||||||
|
|
@ -1736,10 +1741,7 @@ public static partial class AcBinarySerializer
|
||||||
/// Uses the typed Id getter to extract the Id and compares against the scan pass IdentityMap.
|
/// Uses the typed Id getter to extract the Id and compares against the scan pass IdentityMap.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Conditional("DEBUG")]
|
[Conditional("DEBUG")]
|
||||||
private static void ValidateWritePlanObject(
|
private static void ValidateWritePlanObject(in WriteDuplicateEntry planEntry, object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper)
|
||||||
in WriteDuplicateEntry planEntry,
|
|
||||||
object value,
|
|
||||||
TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper)
|
|
||||||
{
|
{
|
||||||
var metadata = wrapper.Metadata;
|
var metadata = wrapper.Metadata;
|
||||||
switch (metadata.IdAccessorType)
|
switch (metadata.IdAccessorType)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue