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>
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.
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<byte> buffer = this.allocator.Allocate<byte>(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<byte> buffer = this.allocator.Allocate<byte>(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;

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

@ -18,6 +18,16 @@ namespace SixLabors.ImageSharp.Tests.IO
/// </summary>
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;
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);

Loading…
Cancel
Save