Browse Source

Use graduated buffers

pull/2006/head
James Jackson-South 4 years ago
parent
commit
1ad9e56137
  1. 60
      src/ImageSharp/IO/ChunkedMemoryStream.cs
  2. 40
      tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs

60
src/ImageSharp/IO/ChunkedMemoryStream.cs

@ -16,39 +16,17 @@ namespace SixLabors.ImageSharp.IO
/// </summary> /// </summary>
internal sealed class ChunkedMemoryStream : Stream internal sealed class ChunkedMemoryStream : Stream
{ {
/// <summary>
/// The default length in bytes of each buffer chunk when allocating large buffers.
/// </summary>
public const int DefaultLargeChunkSize = 1024 * 1024 * 4; // 4 Mb
/// <summary>
/// The threshold at which to switch to using the large buffer.
/// </summary>
public const int DefaultLargeChunkThreshold = DefaultLargeChunkSize / 4; // 1 Mb
/// <summary>
/// The default length in bytes of each buffer chunk when allocating small buffers.
/// </summary>
public const int DefaultSmallChunkSize = DefaultLargeChunkSize / 32; // 128 Kb
// The memory allocator. // The memory allocator.
private readonly MemoryAllocator allocator; private readonly MemoryAllocator allocator;
// Data // Data
private MemoryChunk memoryChunk; private MemoryChunk memoryChunk;
// The length, in bytes, of each large buffer chunk // The total number of allocated chunks
private readonly int largeChunkSize; private int chunkCount;
// 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 allocation length, in bytes // The length of the largest contiguous buffer that can be handled by the allocator.
private int totalAllocation; private readonly int allocatorCapacity;
// Has the stream been disposed. // Has the stream been disposed.
private bool isDisposed; private bool isDisposed;
@ -72,12 +50,7 @@ namespace SixLabors.ImageSharp.IO
{ {
Guard.NotNull(allocator, nameof(allocator)); Guard.NotNull(allocator, nameof(allocator));
// Tweak our buffer sizes to take the minimum of the provided buffer sizes this.allocatorCapacity = allocator.GetBufferCapacityInBytes();
// 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.allocator = allocator; this.allocator = allocator;
} }
@ -236,7 +209,7 @@ namespace SixLabors.ImageSharp.IO
this.memoryChunk = null; this.memoryChunk = null;
this.writeChunk = null; this.writeChunk = null;
this.readChunk = null; this.readChunk = null;
this.totalAllocation = 0; this.chunkCount = 0;
} }
finally finally
{ {
@ -548,13 +521,10 @@ namespace SixLabors.ImageSharp.IO
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private MemoryChunk AllocateMemoryChunk() private MemoryChunk AllocateMemoryChunk()
{ {
if (this.totalAllocation >= this.largeChunkThreshold) // Tweak our buffer sizes to take the minimum of the provided buffer sizes
{ // or the allocator buffer capacity which provides us with the largest
this.chunkSize = this.largeChunkSize; // available contiguous buffer size.
} IMemoryOwner<byte> buffer = this.allocator.Allocate<byte>(Math.Min(this.allocatorCapacity, GetChunkSize(this.chunkCount++)));
IMemoryOwner<byte> buffer = this.allocator.Allocate<byte>(this.chunkSize);
this.totalAllocation += this.chunkSize;
return new MemoryChunk 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 sealed class MemoryChunk : IDisposable
{ {
private bool isDisposed; private bool isDisposed;

40
tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs

@ -18,6 +18,16 @@ namespace SixLabors.ImageSharp.Tests.IO
/// </summary> /// </summary>
public class ChunkedMemoryStreamTests public class ChunkedMemoryStreamTests
{ {
/// <summary>
/// The default length in bytes of each buffer chunk when allocating large buffers.
/// </summary>
private const int DefaultLargeChunkSize = 1024 * 1024 * 4; // 4 Mb
/// <summary>
/// The default length in bytes of each buffer chunk when allocating small buffers.
/// </summary>
private const int DefaultSmallChunkSize = DefaultLargeChunkSize / 32; // 128 Kb
private readonly MemoryAllocator allocator; private readonly MemoryAllocator allocator;
public ChunkedMemoryStreamTests() => this.allocator = Configuration.Default.MemoryAllocator; public ChunkedMemoryStreamTests() => this.allocator = Configuration.Default.MemoryAllocator;
@ -51,11 +61,11 @@ namespace SixLabors.ImageSharp.Tests.IO
} }
[Theory] [Theory]
[InlineData(ChunkedMemoryStream.DefaultSmallChunkSize)] [InlineData(DefaultSmallChunkSize)]
[InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 1.5))] [InlineData((int)(DefaultSmallChunkSize * 1.5))]
[InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 4)] [InlineData(DefaultSmallChunkSize * 4)]
[InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 5.5))] [InlineData((int)(DefaultSmallChunkSize * 5.5))]
[InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 16)] [InlineData(DefaultSmallChunkSize * 16)]
public void MemoryStream_ReadByteTest(int length) public void MemoryStream_ReadByteTest(int length)
{ {
using MemoryStream ms = this.CreateTestStream(length); using MemoryStream ms = this.CreateTestStream(length);
@ -72,11 +82,11 @@ namespace SixLabors.ImageSharp.Tests.IO
} }
[Theory] [Theory]
[InlineData(ChunkedMemoryStream.DefaultSmallChunkSize)] [InlineData(DefaultSmallChunkSize)]
[InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 1.5))] [InlineData((int)(DefaultSmallChunkSize * 1.5))]
[InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 4)] [InlineData(DefaultSmallChunkSize * 4)]
[InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 5.5))] [InlineData((int)(DefaultSmallChunkSize * 5.5))]
[InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 16)] [InlineData(DefaultSmallChunkSize * 16)]
public void MemoryStream_ReadByteBufferTest(int length) public void MemoryStream_ReadByteBufferTest(int length)
{ {
using MemoryStream ms = this.CreateTestStream(length); using MemoryStream ms = this.CreateTestStream(length);
@ -95,11 +105,11 @@ namespace SixLabors.ImageSharp.Tests.IO
} }
[Theory] [Theory]
[InlineData(ChunkedMemoryStream.DefaultSmallChunkSize)] [InlineData(DefaultSmallChunkSize)]
[InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 1.5))] [InlineData((int)(DefaultSmallChunkSize * 1.5))]
[InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 4)] [InlineData(DefaultSmallChunkSize * 4)]
[InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 5.5))] [InlineData((int)(DefaultSmallChunkSize * 5.5))]
[InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 16)] [InlineData(DefaultSmallChunkSize * 16)]
public void MemoryStream_ReadByteBufferSpanTest(int length) public void MemoryStream_ReadByteBufferSpanTest(int length)
{ {
using MemoryStream ms = this.CreateTestStream(length); using MemoryStream ms = this.CreateTestStream(length);

Loading…
Cancel
Save