diff --git a/AyCode.Benchmark/SerializationBenchmarks.cs b/AyCode.Benchmark/SerializationBenchmarks.cs index 471eecf..7fed174 100644 --- a/AyCode.Benchmark/SerializationBenchmarks.cs +++ b/AyCode.Benchmark/SerializationBenchmarks.cs @@ -535,7 +535,7 @@ public abstract class AcBinaryOptionsBenchmarkBase BinaryBenchmarkMode.FastMode => new AcBinarySerializerOptions { UseMetadata = false, - UseStringInterning = false, + UseStringInterning = StringInterningMode.None, ReferenceHandling = ReferenceHandlingMode.None, }, _ => new AcBinarySerializerOptions() diff --git a/AyCode.Core.Serializers.Console/Program.cs b/AyCode.Core.Serializers.Console/Program.cs index c9b461f..246fb91 100644 --- a/AyCode.Core.Serializers.Console/Program.cs +++ b/AyCode.Core.Serializers.Console/Program.cs @@ -121,7 +121,7 @@ public static class Program sharedUser: sharedUser); var options = AcBinarySerializerOptions.WithoutReferenceHandling; - options.UseStringInterning = false; + options.UseStringInterning = StringInterningMode.None; // Warmup (fills caches) System.Console.WriteLine("Warming up (10 iterations)..."); @@ -419,7 +419,7 @@ public static class Program new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.Default, SerializerAcBinaryDefault), new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.WithoutReferenceHandling, SerializerAcBinaryNoRef), new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.FastMode, SerializerAcBinaryFastMode), - new AcBinaryBenchmark(testData.Order, new AcBinarySerializerOptions { UseStringInterning = false }, SerializerAcBinaryNoIntern), + new AcBinaryBenchmark(testData.Order, new AcBinarySerializerOptions { UseStringInterning = StringInterningMode.None }, SerializerAcBinaryNoIntern), // AcJson new AcJsonBenchmark(testData.Order, AcJsonSerializerOptions.Default, SerializerAcJsonDefault), diff --git a/AyCode.Core.Tests/Serialization/AcBinarySerializerBenchmarkTests.cs b/AyCode.Core.Tests/Serialization/AcBinarySerializerBenchmarkTests.cs index 7c0f194..03e538d 100644 --- a/AyCode.Core.Tests/Serialization/AcBinarySerializerBenchmarkTests.cs +++ b/AyCode.Core.Tests/Serialization/AcBinarySerializerBenchmarkTests.cs @@ -62,7 +62,7 @@ public class AcBinarySerializerBenchmarkTests var order = TestDataFactory.CreateBenchmarkOrder(itemCount: 5, palletsPerItem: 3, measurementsPerPallet: 2, pointsPerMeasurement: 5); var binaryWithInterning = AcBinarySerializer.Serialize(order, AcBinarySerializerOptions.Default); - var binaryWithoutInterning = AcBinarySerializer.Serialize(order, new AcBinarySerializerOptions { UseStringInterning = false }); + var binaryWithoutInterning = AcBinarySerializer.Serialize(order, new AcBinarySerializerOptions { UseStringInterning = StringInterningMode.None }); // Note: String interning may not always result in smaller size due to header overhead // The primary benefit is for larger datasets with many repeated strings diff --git a/AyCode.Core.Tests/Serialization/AcBinarySerializerStringInterningTests.cs b/AyCode.Core.Tests/Serialization/AcBinarySerializerStringInterningTests.cs index d71d16b..97a37d0 100644 --- a/AyCode.Core.Tests/Serialization/AcBinarySerializerStringInterningTests.cs +++ b/AyCode.Core.Tests/Serialization/AcBinarySerializerStringInterningTests.cs @@ -24,7 +24,7 @@ public class AcBinarySerializerStringInterningTests var binaryWithInterning = AcBinarySerializer.Serialize(obj, AcBinarySerializerOptions.Default); var binaryWithoutInterning = AcBinarySerializer.Serialize(obj, - new AcBinarySerializerOptions { UseStringInterning = false }); + new AcBinarySerializerOptions { UseStringInterning = StringInterningMode.None }); Assert.IsTrue(binaryWithInterning.Length < binaryWithoutInterning.Length, $"With interning: {binaryWithInterning.Length}, Without: {binaryWithoutInterning.Length}"); diff --git a/AyCode.Core.Tests/Serialization/QuickBenchmark.cs b/AyCode.Core.Tests/Serialization/QuickBenchmark.cs index 97c9ec1..2d9b9a6 100644 --- a/AyCode.Core.Tests/Serialization/QuickBenchmark.cs +++ b/AyCode.Core.Tests/Serialization/QuickBenchmark.cs @@ -197,7 +197,7 @@ public class QuickBenchmark var withInterningMs = sw.Elapsed.TotalMilliseconds; // Without interning - var noInternOptions = new AcBinarySerializerOptions { UseStringInterning = false }; + var noInternOptions = new AcBinarySerializerOptions { UseStringInterning = StringInterningMode.None }; sw.Restart(); byte[] withoutInterning = null!; for (int i = 0; i < iterations; i++) @@ -427,7 +427,7 @@ public class QuickBenchmark sharedMetadata: sharedMeta); var singleOptions = AcBinarySerializerOptions.FastMode; - singleOptions.UseStringInterning = false; + singleOptions.UseStringInterning = StringInterningMode.None; Console.WriteLine("=== MINIMAL WARMUP TEST ==="); Console.WriteLine(); @@ -507,9 +507,9 @@ public class QuickBenchmark // Options var withRefOptions = AcBinarySerializerOptions.Default; - //withRefOptions.UseStringInterning = false; + //withRefOptions.UseStringInterning = StringInterningMode.None; var noRefOptions = AcBinarySerializerOptions.WithoutReferenceHandling; - noRefOptions.UseStringInterning = false; + noRefOptions.UseStringInterning = StringInterningMode.None; // Pre-serialize var acBinaryWithRef = AcBinarySerializer.Serialize(testOrder, withRefOptions); diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs index 7ac0d3c..1628650 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs @@ -92,7 +92,7 @@ public static partial class AcBinarySerializer #endif // These properties delegate to Options for convenience - public bool UseStringInterning => Options.UseStringInterning; + public bool UseStringInterning => Options.UseStringInterning != StringInterningMode.None; public bool UseMetadata => Options.UseMetadata; public byte MinStringInternLength => Options.MinStringInternLength; public byte MaxStringInternLength => Options.MaxStringInternLength; diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs index ec5d747..6de9d2a 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs @@ -60,9 +60,9 @@ public static partial class AcBinarySerializer options ??= AcBinarySerializerOptions.Default; // For analysis, use the provided reference handling mode - var analysisOptions = new AcBinarySerializerOptions - { - UseStringInterning = true, + var analysisOptions = new AcBinarySerializerOptions + { + UseStringInterning = StringInterningMode.All, MinStringInternLength = options.MinStringInternLength, MaxStringInternLength = options.MaxStringInternLength, ReferenceHandling = options.ReferenceHandling diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs index 767b510..4edf5c1 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs @@ -33,7 +33,7 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions /// public static AcBinarySerializerOptions FastMode => new() { - UseStringInterning = false, + UseStringInterning = StringInterningMode.None, ReferenceHandling = ReferenceHandlingMode.None }; @@ -44,7 +44,7 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions public static AcBinarySerializerOptions ShallowCopy => new() { MaxDepth = 0, - UseStringInterning = false, + UseStringInterning = StringInterningMode.None, ReferenceHandling = ReferenceHandlingMode.None }; @@ -89,12 +89,13 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions public bool UseMetadata { get; init; } = false; /// - /// Whether to intern repeated strings. - /// When enabled, duplicate strings are stored once and referenced by index. - /// Reduces size and memory for objects with many repeated string values. - /// Default: true + /// Controls how string interning is applied during serialization. + /// None: No interning, all strings written inline. + /// Attribute: Only properties with [AcStringIntern(true)] are interned. + /// All: All strings within length limits are interned (legacy behavior). + /// Default: All /// - public bool UseStringInterning { get; set; } = true; + public StringInterningMode UseStringInterning { get; set; } = StringInterningMode.All; /// /// Minimum string length to consider for interning. diff --git a/AyCode.Core/Serializers/Binaries/AcStringInternAttribute.cs b/AyCode.Core/Serializers/Binaries/AcStringInternAttribute.cs new file mode 100644 index 0000000..5d30d37 --- /dev/null +++ b/AyCode.Core/Serializers/Binaries/AcStringInternAttribute.cs @@ -0,0 +1,25 @@ +namespace AyCode.Core.Serializers.Binaries; + +/// +/// Controls string interning for a specific property during binary serialization. +/// When StringInterningMode is Attribute: only properties with [AcStringIntern(true)] are interned. +/// When StringInterningMode is All: properties with [AcStringIntern(false)] opt-out from interning. +/// Attribute is inherited from base class properties if not explicitly specified. +/// +[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)] +public sealed class AcStringInternAttribute : Attribute +{ + /// + /// Whether string interning is enabled for this property. + /// + public bool Enabled { get; } + + /// + /// Creates a new AcStringInternAttribute. + /// + /// True to enable interning, false to disable. + public AcStringInternAttribute(bool enabled) + { + Enabled = enabled; + } +} diff --git a/AyCode.Core/Serializers/Binaries/StringInterningMode.cs b/AyCode.Core/Serializers/Binaries/StringInterningMode.cs new file mode 100644 index 0000000..f69e872 --- /dev/null +++ b/AyCode.Core/Serializers/Binaries/StringInterningMode.cs @@ -0,0 +1,24 @@ +namespace AyCode.Core.Serializers.Binaries; + +/// +/// Controls string interning behavior during binary serialization. +/// +public enum StringInterningMode +{ + /// + /// No string interning. All strings are written inline. + /// + None = 0, + + /// + /// Only intern strings on properties marked with [AcStringIntern(true)]. + /// Properties without attribute or with [AcStringIntern(false)] are written inline. + /// + Attribute = 1, + + /// + /// Intern all strings that meet the length criteria (MinStringInternLength to MaxStringInternLength). + /// [AcStringIntern(false)] can opt-out specific properties. + /// + All = 2 +}