diff options
author | Andrew Arnott <andrew.arnott@microsoft.com> | 2022-06-23 20:01:23 +0300 |
---|---|---|
committer | Andrew Arnott <andrew.arnott@microsoft.com> | 2022-06-23 20:32:55 +0300 |
commit | 9a2acd8e1bcc843e4ab9ff704f688749d526ee88 (patch) | |
tree | 30b8a2665d920bf7ca4611e9251c411f3524d40f | |
parent | 888ecb280ba620a2de1d8445b6661fb3e22ffcf7 (diff) |
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.
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; /// <summary> @@ -349,7 +348,7 @@ namespace MessagePack do { cancellationToken.ThrowIfCancellationRequested(); - Span<byte> span = sequence.GetSpan(stream.CanSeek ? (int)Math.Min(MaxHintSize, stream.Length - stream.Position) : 0); + Span<byte> 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<byte> memory = sequence.GetMemory(stream.CanSeek ? (int)Math.Min(MaxHintSize, stream.Length - stream.Position) : 0); + Memory<byte> 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; @@ -93,6 +94,16 @@ namespace MessagePack public int CompressionMinLength { get; private set; } = 64; /// <summary> + /// Gets the size of contiguous memory blocks in bytes that may be allocated for buffering purposes. + /// </summary> + /// <value>The default value is 1MB.</value> + /// <remarks> + /// Larger values may perform a bit faster, but may result in adding a runtime perf tax due to using the + /// <see href="https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/large-object-heap">Large Object Heap</see>. + /// </remarks> + public int SuggestedContiguousMemorySize { get; private set; } = 1024 * 1024; + + /// <summary> /// Gets a value indicating whether to serialize with <see cref="MessagePackWriter.OldSpec"/> set to some value /// causing messagepack spec compliance to be explicitly set to the old or new format. /// </summary> @@ -228,6 +239,28 @@ namespace MessagePack } /// <summary> + /// Gets a copy of these options with the <see cref="SuggestedContiguousMemorySize"/> property set to a new value. + /// </summary> + /// <param name="suggestedContiguousMemorySize">The new value for the <see cref="SuggestedContiguousMemorySize"/> property. Must be at least 256.</param> + /// <returns>The new instance; or the original if the value is unchanged.</returns> + 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; + } + + /// <summary> /// Gets a copy of these options with the <see cref="OldSpec"/> property set to a new value. /// </summary> /// <param name="oldSpec">The new value for the <see cref="OldSpec"/>.</param> 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() @@ -48,6 +49,16 @@ public class MessagePackSerializerOptionsTests } [Fact] + public void SuggestedContiguousMemorySize() + { + Assert.Equal(1024 * 1024, MessagePackSerializerOptions.Standard.SuggestedContiguousMemorySize); + Assert.Throws<ArgumentOutOfRangeException>(() => MessagePackSerializerOptions.Standard.WithSuggestedContiguousMemorySize(0)); + Assert.Throws<ArgumentOutOfRangeException>(() => MessagePackSerializerOptions.Standard.WithSuggestedContiguousMemorySize(4)); + MessagePackSerializerOptions options = MessagePackSerializerOptions.Standard.WithSuggestedContiguousMemorySize(512); + Assert.Equal(512, options.SuggestedContiguousMemorySize); + } + + [Fact] public void OldSpec() { Assert.Null(MessagePackSerializerOptions.Standard.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); @@ -93,11 +105,25 @@ public class MessagePackSerializerOptionsTests } [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 |