From 9a2acd8e1bcc843e4ab9ff704f688749d526ee88 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Thu, 23 Jun 2022 11:01:23 -0600 Subject: Add option to avoid large buffer allocations Allocating 1MB buffers contribute to the large object heap and should be avoidable for applications that it is a problem for. --- .../Scripts/MessagePack/MessagePackSerializer.cs | 5 ++-- .../MessagePack/MessagePackSerializerOptions.cs | 33 ++++++++++++++++++++++ .../MessagePackSerializerOptionsTests.cs | 31 +++++++++++++++++++- src/MessagePack/net6.0/PublicAPI.Unshipped.txt | 2 ++ .../netcoreapp3.1/PublicAPI.Unshipped.txt | 2 ++ .../netstandard2.0/PublicAPI.Unshipped.txt | 4 ++- 6 files changed, 72 insertions(+), 5 deletions(-) diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializer.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializer.cs index 24b5373c..8c4a454c 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializer.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializer.cs @@ -18,7 +18,6 @@ namespace MessagePack [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Each overload has sufficiently unique required parameters.")] public static partial class MessagePackSerializer { - private const int MaxHintSize = 1024 * 1024; private static MessagePackSerializerOptions defaultOptions; /// @@ -349,7 +348,7 @@ namespace MessagePack do { cancellationToken.ThrowIfCancellationRequested(); - Span span = sequence.GetSpan(stream.CanSeek ? (int)Math.Min(MaxHintSize, stream.Length - stream.Position) : 0); + Span span = sequence.GetSpan(stream.CanSeek ? (int)Math.Min(options.SuggestedContiguousMemorySize, stream.Length - stream.Position) : 0); bytesRead = stream.Read(span); sequence.Advance(bytesRead); } @@ -397,7 +396,7 @@ namespace MessagePack int bytesRead; do { - Memory memory = sequence.GetMemory(stream.CanSeek ? (int)Math.Min(MaxHintSize, stream.Length - stream.Position) : 0); + Memory memory = sequence.GetMemory(stream.CanSeek ? (int)Math.Min(options.SuggestedContiguousMemorySize, stream.Length - stream.Position) : 0); bytesRead = await stream.ReadAsync(memory, cancellationToken).ConfigureAwait(false); sequence.Advance(bytesRead); } diff --git a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializerOptions.cs b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializerOptions.cs index 771aa25f..a9a9ec51 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializerOptions.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/MessagePackSerializerOptions.cs @@ -58,6 +58,7 @@ namespace MessagePack this.Resolver = copyFrom.Resolver; this.Compression = copyFrom.Compression; this.CompressionMinLength = copyFrom.CompressionMinLength; + this.SuggestedContiguousMemorySize = copyFrom.SuggestedContiguousMemorySize; this.OldSpec = copyFrom.OldSpec; this.OmitAssemblyVersion = copyFrom.OmitAssemblyVersion; this.AllowAssemblyVersionMismatch = copyFrom.AllowAssemblyVersionMismatch; @@ -92,6 +93,16 @@ namespace MessagePack /// public int CompressionMinLength { get; private set; } = 64; + /// + /// Gets the size of contiguous memory blocks in bytes that may be allocated for buffering purposes. + /// + /// The default value is 1MB. + /// + /// Larger values may perform a bit faster, but may result in adding a runtime perf tax due to using the + /// Large Object Heap. + /// + public int SuggestedContiguousMemorySize { get; private set; } = 1024 * 1024; + /// /// Gets a value indicating whether to serialize with set to some value /// causing messagepack spec compliance to be explicitly set to the old or new format. @@ -227,6 +238,28 @@ namespace MessagePack return result; } + /// + /// Gets a copy of these options with the property set to a new value. + /// + /// The new value for the property. Must be at least 256. + /// The new instance; or the original if the value is unchanged. + public MessagePackSerializerOptions WithSuggestedContiguousMemorySize(int suggestedContiguousMemorySize) + { + if (this.SuggestedContiguousMemorySize == suggestedContiguousMemorySize) + { + return this; + } + + if (suggestedContiguousMemorySize < 256) + { + throw new ArgumentOutOfRangeException(nameof(suggestedContiguousMemorySize), "This should be at least 256"); + } + + var result = this.Clone(); + result.SuggestedContiguousMemorySize = suggestedContiguousMemorySize; + return result; + } + /// /// Gets a copy of these options with the property set to a new value. /// diff --git a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackSerializerOptionsTests.cs b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackSerializerOptionsTests.cs index dfe988f8..0db5157c 100644 --- a/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackSerializerOptionsTests.cs +++ b/src/MessagePack.UnityClient/Assets/Scripts/Tests/ShareTests/MessagePackSerializerOptionsTests.cs @@ -14,7 +14,8 @@ public class MessagePackSerializerOptionsTests .WithOmitAssemblyVersion(true) .WithResolver(BuiltinResolver.Instance) .WithOldSpec(false) - .WithSecurity(MySecurityOptions.Instance); + .WithSecurity(MySecurityOptions.Instance) + .WithSuggestedContiguousMemorySize(64 * 1024); [Fact] public void AllowAssemblyVersionMismatch() @@ -47,6 +48,16 @@ public class MessagePackSerializerOptionsTests Assert.Equal(128, options.CompressionMinLength); } + [Fact] + public void SuggestedContiguousMemorySize() + { + Assert.Equal(1024 * 1024, MessagePackSerializerOptions.Standard.SuggestedContiguousMemorySize); + Assert.Throws(() => MessagePackSerializerOptions.Standard.WithSuggestedContiguousMemorySize(0)); + Assert.Throws(() => MessagePackSerializerOptions.Standard.WithSuggestedContiguousMemorySize(4)); + MessagePackSerializerOptions options = MessagePackSerializerOptions.Standard.WithSuggestedContiguousMemorySize(512); + Assert.Equal(512, options.SuggestedContiguousMemorySize); + } + [Fact] public void OldSpec() { @@ -74,6 +85,7 @@ public class MessagePackSerializerOptionsTests var mutated = NonDefaultOptions.WithOldSpec(true); Assert.True(mutated.OldSpec.Value); Assert.Equal(NonDefaultOptions.Compression, mutated.Compression); + Assert.Equal(NonDefaultOptions.SuggestedContiguousMemorySize, mutated.SuggestedContiguousMemorySize); Assert.Equal(NonDefaultOptions.AllowAssemblyVersionMismatch, mutated.AllowAssemblyVersionMismatch); Assert.Equal(NonDefaultOptions.OmitAssemblyVersion, mutated.OmitAssemblyVersion); Assert.Same(NonDefaultOptions.Resolver, mutated.Resolver); @@ -92,12 +104,26 @@ public class MessagePackSerializerOptionsTests Assert.Same(MySecurityOptions.Instance, mutated.Security); } + [Fact] + public void WithSuggestedContiguousMemorySize_PreservesOtherProperties() + { + var mutated = NonDefaultOptions.WithSuggestedContiguousMemorySize(612); + Assert.Equal(612, mutated.SuggestedContiguousMemorySize); + Assert.Equal(NonDefaultOptions.Compression, mutated.Compression); + Assert.Equal(NonDefaultOptions.OldSpec, mutated.OldSpec); + Assert.Equal(NonDefaultOptions.AllowAssemblyVersionMismatch, mutated.AllowAssemblyVersionMismatch); + Assert.Equal(NonDefaultOptions.OmitAssemblyVersion, mutated.OmitAssemblyVersion); + Assert.Same(NonDefaultOptions.Resolver, mutated.Resolver); + Assert.Same(MySecurityOptions.Instance, mutated.Security); + } + [Fact] public void WithAllowAssemblyVersionMismatch_PreservesOtherProperties() { var mutated = NonDefaultOptions.WithAllowAssemblyVersionMismatch(false); Assert.False(mutated.AllowAssemblyVersionMismatch); Assert.Equal(NonDefaultOptions.Compression, mutated.Compression); + Assert.Equal(NonDefaultOptions.SuggestedContiguousMemorySize, mutated.SuggestedContiguousMemorySize); Assert.Equal(NonDefaultOptions.OldSpec, mutated.OldSpec); Assert.Equal(NonDefaultOptions.OmitAssemblyVersion, mutated.OmitAssemblyVersion); Assert.Same(NonDefaultOptions.Resolver, mutated.Resolver); @@ -110,6 +136,7 @@ public class MessagePackSerializerOptionsTests var mutated = NonDefaultOptions.WithOmitAssemblyVersion(false); Assert.False(mutated.OmitAssemblyVersion); Assert.Equal(NonDefaultOptions.Compression, mutated.Compression); + Assert.Equal(NonDefaultOptions.SuggestedContiguousMemorySize, mutated.SuggestedContiguousMemorySize); Assert.Equal(NonDefaultOptions.OldSpec, mutated.OldSpec); Assert.Equal(NonDefaultOptions.AllowAssemblyVersionMismatch, mutated.AllowAssemblyVersionMismatch); Assert.Same(NonDefaultOptions.Resolver, mutated.Resolver); @@ -122,6 +149,7 @@ public class MessagePackSerializerOptionsTests var mutated = NonDefaultOptions.WithResolver(ContractlessStandardResolver.Instance); Assert.Same(ContractlessStandardResolver.Instance, mutated.Resolver); Assert.Equal(NonDefaultOptions.Compression, mutated.Compression); + Assert.Equal(NonDefaultOptions.SuggestedContiguousMemorySize, mutated.SuggestedContiguousMemorySize); Assert.Equal(NonDefaultOptions.OldSpec, mutated.OldSpec); Assert.Equal(NonDefaultOptions.AllowAssemblyVersionMismatch, mutated.AllowAssemblyVersionMismatch); Assert.Equal(NonDefaultOptions.OmitAssemblyVersion, mutated.OmitAssemblyVersion); @@ -135,6 +163,7 @@ public class MessagePackSerializerOptionsTests Assert.Same(MessagePackSecurity.TrustedData, mutated.Security); Assert.Same(NonDefaultOptions.Resolver, mutated.Resolver); Assert.Equal(NonDefaultOptions.Compression, mutated.Compression); + Assert.Equal(NonDefaultOptions.SuggestedContiguousMemorySize, mutated.SuggestedContiguousMemorySize); Assert.Equal(NonDefaultOptions.OldSpec, mutated.OldSpec); Assert.Equal(NonDefaultOptions.AllowAssemblyVersionMismatch, mutated.AllowAssemblyVersionMismatch); Assert.Equal(NonDefaultOptions.OmitAssemblyVersion, mutated.OmitAssemblyVersion); diff --git a/src/MessagePack/net6.0/PublicAPI.Unshipped.txt b/src/MessagePack/net6.0/PublicAPI.Unshipped.txt index 78006a92..89697310 100644 --- a/src/MessagePack/net6.0/PublicAPI.Unshipped.txt +++ b/src/MessagePack/net6.0/PublicAPI.Unshipped.txt @@ -9,6 +9,8 @@ MessagePack.Formatters.TimeOnlyFormatter MessagePack.Formatters.TimeOnlyFormatter.Deserialize(ref MessagePack.MessagePackReader reader, MessagePack.MessagePackSerializerOptions options) -> System.TimeOnly MessagePack.Formatters.TimeOnlyFormatter.Serialize(ref MessagePack.MessagePackWriter writer, System.TimeOnly value, MessagePack.MessagePackSerializerOptions options) -> void MessagePack.MessagePackSerializerOptions.CompressionMinLength.get -> int +MessagePack.MessagePackSerializerOptions.SuggestedContiguousMemorySize.get -> int MessagePack.MessagePackSerializerOptions.WithCompressionMinLength(int compressionMinLength) -> MessagePack.MessagePackSerializerOptions +MessagePack.MessagePackSerializerOptions.WithSuggestedContiguousMemorySize(int suggestedContiguousMemorySize) -> MessagePack.MessagePackSerializerOptions static readonly MessagePack.Formatters.DateOnlyFormatter.Instance -> MessagePack.Formatters.DateOnlyFormatter static readonly MessagePack.Formatters.TimeOnlyFormatter.Instance -> MessagePack.Formatters.TimeOnlyFormatter diff --git a/src/MessagePack/netcoreapp3.1/PublicAPI.Unshipped.txt b/src/MessagePack/netcoreapp3.1/PublicAPI.Unshipped.txt index 9ba64088..ba449cac 100644 --- a/src/MessagePack/netcoreapp3.1/PublicAPI.Unshipped.txt +++ b/src/MessagePack/netcoreapp3.1/PublicAPI.Unshipped.txt @@ -3,4 +3,6 @@ MessagePack.Formatters.StringInterningFormatter.Deserialize(ref MessagePack.Mess MessagePack.Formatters.StringInterningFormatter.Serialize(ref MessagePack.MessagePackWriter writer, string value, MessagePack.MessagePackSerializerOptions options) -> void MessagePack.Formatters.StringInterningFormatter.StringInterningFormatter() -> void MessagePack.MessagePackSerializerOptions.CompressionMinLength.get -> int +MessagePack.MessagePackSerializerOptions.SuggestedContiguousMemorySize.get -> int MessagePack.MessagePackSerializerOptions.WithCompressionMinLength(int compressionMinLength) -> MessagePack.MessagePackSerializerOptions +MessagePack.MessagePackSerializerOptions.WithSuggestedContiguousMemorySize(int suggestedContiguousMemorySize) -> MessagePack.MessagePackSerializerOptions diff --git a/src/MessagePack/netstandard2.0/PublicAPI.Unshipped.txt b/src/MessagePack/netstandard2.0/PublicAPI.Unshipped.txt index 49e84ad1..2cdd3d32 100644 --- a/src/MessagePack/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/MessagePack/netstandard2.0/PublicAPI.Unshipped.txt @@ -3,4 +3,6 @@ MessagePack.Formatters.StringInterningFormatter.Deserialize(ref MessagePack.Mess MessagePack.Formatters.StringInterningFormatter.Serialize(ref MessagePack.MessagePackWriter writer, string value, MessagePack.MessagePackSerializerOptions options) -> void MessagePack.Formatters.StringInterningFormatter.StringInterningFormatter() -> void MessagePack.MessagePackSerializerOptions.CompressionMinLength.get -> int -MessagePack.MessagePackSerializerOptions.WithCompressionMinLength(int compressionMinLength) -> MessagePack.MessagePackSerializerOptions \ No newline at end of file +MessagePack.MessagePackSerializerOptions.SuggestedContiguousMemorySize.get -> int +MessagePack.MessagePackSerializerOptions.WithCompressionMinLength(int compressionMinLength) -> MessagePack.MessagePackSerializerOptions +MessagePack.MessagePackSerializerOptions.WithSuggestedContiguousMemorySize(int suggestedContiguousMemorySize) -> MessagePack.MessagePackSerializerOptions \ No newline at end of file -- cgit v1.2.3