diff --git a/src/ImageSharp/IO/ChunkedMemoryStream.cs b/src/ImageSharp/IO/ChunkedMemoryStream.cs index 9c242b5585..c34ed9878e 100644 --- a/src/ImageSharp/IO/ChunkedMemoryStream.cs +++ b/src/ImageSharp/IO/ChunkedMemoryStream.cs @@ -16,39 +16,17 @@ namespace SixLabors.ImageSharp.IO /// internal sealed class ChunkedMemoryStream : Stream { - /// - /// The default length in bytes of each buffer chunk when allocating large buffers. - /// - public const int DefaultLargeChunkSize = 1024 * 1024 * 4; // 4 Mb - - /// - /// The threshold at which to switch to using the large buffer. - /// - public const int DefaultLargeChunkThreshold = DefaultLargeChunkSize / 4; // 1 Mb - - /// - /// The default length in bytes of each buffer chunk when allocating small buffers. - /// - public const int DefaultSmallChunkSize = DefaultLargeChunkSize / 32; // 128 Kb - // The memory allocator. private readonly MemoryAllocator allocator; // Data private MemoryChunk memoryChunk; - // The length, in bytes, of each large buffer chunk - private readonly int largeChunkSize; - - // The length, in bytes of the total allocation threshold that triggers switching from - // small to large buffer chunks. - private readonly int largeChunkThreshold; - - // The current length, in bytes, of each buffer chunk - private int chunkSize; + // The total number of allocated chunks + private int chunkCount; - // The total allocation length, in bytes - private int totalAllocation; + // The length of the largest contiguous buffer that can be handled by the allocator. + private readonly int allocatorCapacity; // Has the stream been disposed. private bool isDisposed; @@ -72,12 +50,7 @@ namespace SixLabors.ImageSharp.IO { Guard.NotNull(allocator, nameof(allocator)); - // Tweak our buffer sizes to take the minimum of the provided buffer sizes - // or values scaled from the allocator buffer capacity which provides us with the largest - // available contiguous buffer size. - this.largeChunkSize = Math.Min(DefaultLargeChunkSize, allocator.GetBufferCapacityInBytes()); - this.largeChunkThreshold = Math.Min(DefaultLargeChunkThreshold, this.largeChunkSize / 4); - this.chunkSize = Math.Min(DefaultSmallChunkSize, this.largeChunkSize / 32); + this.allocatorCapacity = allocator.GetBufferCapacityInBytes(); this.allocator = allocator; } @@ -236,7 +209,7 @@ namespace SixLabors.ImageSharp.IO this.memoryChunk = null; this.writeChunk = null; this.readChunk = null; - this.totalAllocation = 0; + this.chunkCount = 0; } finally { @@ -548,13 +521,10 @@ namespace SixLabors.ImageSharp.IO [MethodImpl(MethodImplOptions.AggressiveInlining)] private MemoryChunk AllocateMemoryChunk() { - if (this.totalAllocation >= this.largeChunkThreshold) - { - this.chunkSize = this.largeChunkSize; - } - - IMemoryOwner buffer = this.allocator.Allocate(this.chunkSize); - this.totalAllocation += this.chunkSize; + // Tweak our buffer sizes to take the minimum of the provided buffer sizes + // or the allocator buffer capacity which provides us with the largest + // available contiguous buffer size. + IMemoryOwner buffer = this.allocator.Allocate(Math.Min(this.allocatorCapacity, GetChunkSize(this.chunkCount++))); return new MemoryChunk { @@ -573,6 +543,16 @@ namespace SixLabors.ImageSharp.IO } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetChunkSize(int i) + { +#pragma warning disable IDE1006 // Naming Styles + const int _128K = 1 << 17; + const int _4M = 1 << 22; + return i < 16 ? _128K * (1 << (i / 4)) : _4M; +#pragma warning restore IDE1006 // Naming Styles + } + private sealed class MemoryChunk : IDisposable { private bool isDisposed; diff --git a/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs b/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs index 455730e714..6bcb7e7ad3 100644 --- a/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs +++ b/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs @@ -18,6 +18,16 @@ namespace SixLabors.ImageSharp.Tests.IO /// public class ChunkedMemoryStreamTests { + /// + /// The default length in bytes of each buffer chunk when allocating large buffers. + /// + private const int DefaultLargeChunkSize = 1024 * 1024 * 4; // 4 Mb + + /// + /// The default length in bytes of each buffer chunk when allocating small buffers. + /// + private const int DefaultSmallChunkSize = DefaultLargeChunkSize / 32; // 128 Kb + private readonly MemoryAllocator allocator; public ChunkedMemoryStreamTests() => this.allocator = Configuration.Default.MemoryAllocator; @@ -51,11 +61,11 @@ namespace SixLabors.ImageSharp.Tests.IO } [Theory] - [InlineData(ChunkedMemoryStream.DefaultSmallChunkSize)] - [InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 1.5))] - [InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 4)] - [InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 5.5))] - [InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 16)] + [InlineData(DefaultSmallChunkSize)] + [InlineData((int)(DefaultSmallChunkSize * 1.5))] + [InlineData(DefaultSmallChunkSize * 4)] + [InlineData((int)(DefaultSmallChunkSize * 5.5))] + [InlineData(DefaultSmallChunkSize * 16)] public void MemoryStream_ReadByteTest(int length) { using MemoryStream ms = this.CreateTestStream(length); @@ -72,11 +82,11 @@ namespace SixLabors.ImageSharp.Tests.IO } [Theory] - [InlineData(ChunkedMemoryStream.DefaultSmallChunkSize)] - [InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 1.5))] - [InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 4)] - [InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 5.5))] - [InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 16)] + [InlineData(DefaultSmallChunkSize)] + [InlineData((int)(DefaultSmallChunkSize * 1.5))] + [InlineData(DefaultSmallChunkSize * 4)] + [InlineData((int)(DefaultSmallChunkSize * 5.5))] + [InlineData(DefaultSmallChunkSize * 16)] public void MemoryStream_ReadByteBufferTest(int length) { using MemoryStream ms = this.CreateTestStream(length); @@ -95,11 +105,11 @@ namespace SixLabors.ImageSharp.Tests.IO } [Theory] - [InlineData(ChunkedMemoryStream.DefaultSmallChunkSize)] - [InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 1.5))] - [InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 4)] - [InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 5.5))] - [InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 16)] + [InlineData(DefaultSmallChunkSize)] + [InlineData((int)(DefaultSmallChunkSize * 1.5))] + [InlineData(DefaultSmallChunkSize * 4)] + [InlineData((int)(DefaultSmallChunkSize * 5.5))] + [InlineData(DefaultSmallChunkSize * 16)] public void MemoryStream_ReadByteBufferSpanTest(int length) { using MemoryStream ms = this.CreateTestStream(length);