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);