Skip to content

Commit 1ad9e56

Browse files
Use graduated buffers
1 parent faea7d0 commit 1ad9e56

File tree

2 files changed

+45
-55
lines changed

2 files changed

+45
-55
lines changed

src/ImageSharp/IO/ChunkedMemoryStream.cs

Lines changed: 20 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -16,39 +16,17 @@ namespace SixLabors.ImageSharp.IO
1616
/// </summary>
1717
internal sealed class ChunkedMemoryStream : Stream
1818
{
19-
/// <summary>
20-
/// The default length in bytes of each buffer chunk when allocating large buffers.
21-
/// </summary>
22-
public const int DefaultLargeChunkSize = 1024 * 1024 * 4; // 4 Mb
23-
24-
/// <summary>
25-
/// The threshold at which to switch to using the large buffer.
26-
/// </summary>
27-
public const int DefaultLargeChunkThreshold = DefaultLargeChunkSize / 4; // 1 Mb
28-
29-
/// <summary>
30-
/// The default length in bytes of each buffer chunk when allocating small buffers.
31-
/// </summary>
32-
public const int DefaultSmallChunkSize = DefaultLargeChunkSize / 32; // 128 Kb
33-
3419
// The memory allocator.
3520
private readonly MemoryAllocator allocator;
3621

3722
// Data
3823
private MemoryChunk memoryChunk;
3924

40-
// The length, in bytes, of each large buffer chunk
41-
private readonly int largeChunkSize;
42-
43-
// The length, in bytes of the total allocation threshold that triggers switching from
44-
// small to large buffer chunks.
45-
private readonly int largeChunkThreshold;
46-
47-
// The current length, in bytes, of each buffer chunk
48-
private int chunkSize;
25+
// The total number of allocated chunks
26+
private int chunkCount;
4927

50-
// The total allocation length, in bytes
51-
private int totalAllocation;
28+
// The length of the largest contiguous buffer that can be handled by the allocator.
29+
private readonly int allocatorCapacity;
5230

5331
// Has the stream been disposed.
5432
private bool isDisposed;
@@ -72,12 +50,7 @@ public ChunkedMemoryStream(MemoryAllocator allocator)
7250
{
7351
Guard.NotNull(allocator, nameof(allocator));
7452

75-
// Tweak our buffer sizes to take the minimum of the provided buffer sizes
76-
// or values scaled from the allocator buffer capacity which provides us with the largest
77-
// available contiguous buffer size.
78-
this.largeChunkSize = Math.Min(DefaultLargeChunkSize, allocator.GetBufferCapacityInBytes());
79-
this.largeChunkThreshold = Math.Min(DefaultLargeChunkThreshold, this.largeChunkSize / 4);
80-
this.chunkSize = Math.Min(DefaultSmallChunkSize, this.largeChunkSize / 32);
53+
this.allocatorCapacity = allocator.GetBufferCapacityInBytes();
8154
this.allocator = allocator;
8255
}
8356

@@ -236,7 +209,7 @@ protected override void Dispose(bool disposing)
236209
this.memoryChunk = null;
237210
this.writeChunk = null;
238211
this.readChunk = null;
239-
this.totalAllocation = 0;
212+
this.chunkCount = 0;
240213
}
241214
finally
242215
{
@@ -548,13 +521,10 @@ private void EnsureNotDisposed()
548521
[MethodImpl(MethodImplOptions.AggressiveInlining)]
549522
private MemoryChunk AllocateMemoryChunk()
550523
{
551-
if (this.totalAllocation >= this.largeChunkThreshold)
552-
{
553-
this.chunkSize = this.largeChunkSize;
554-
}
555-
556-
IMemoryOwner<byte> buffer = this.allocator.Allocate<byte>(this.chunkSize);
557-
this.totalAllocation += this.chunkSize;
524+
// Tweak our buffer sizes to take the minimum of the provided buffer sizes
525+
// or the allocator buffer capacity which provides us with the largest
526+
// available contiguous buffer size.
527+
IMemoryOwner<byte> buffer = this.allocator.Allocate<byte>(Math.Min(this.allocatorCapacity, GetChunkSize(this.chunkCount++)));
558528

559529
return new MemoryChunk
560530
{
@@ -573,6 +543,16 @@ private void ReleaseMemoryChunks(MemoryChunk chunk)
573543
}
574544
}
575545

546+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
547+
private static int GetChunkSize(int i)
548+
{
549+
#pragma warning disable IDE1006 // Naming Styles
550+
const int _128K = 1 << 17;
551+
const int _4M = 1 << 22;
552+
return i < 16 ? _128K * (1 << (i / 4)) : _4M;
553+
#pragma warning restore IDE1006 // Naming Styles
554+
}
555+
576556
private sealed class MemoryChunk : IDisposable
577557
{
578558
private bool isDisposed;

tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,16 @@ namespace SixLabors.ImageSharp.Tests.IO
1818
/// </summary>
1919
public class ChunkedMemoryStreamTests
2020
{
21+
/// <summary>
22+
/// The default length in bytes of each buffer chunk when allocating large buffers.
23+
/// </summary>
24+
private const int DefaultLargeChunkSize = 1024 * 1024 * 4; // 4 Mb
25+
26+
/// <summary>
27+
/// The default length in bytes of each buffer chunk when allocating small buffers.
28+
/// </summary>
29+
private const int DefaultSmallChunkSize = DefaultLargeChunkSize / 32; // 128 Kb
30+
2131
private readonly MemoryAllocator allocator;
2232

2333
public ChunkedMemoryStreamTests() => this.allocator = Configuration.Default.MemoryAllocator;
@@ -51,11 +61,11 @@ public void MemoryStream_ReadTest_Negative()
5161
}
5262

5363
[Theory]
54-
[InlineData(ChunkedMemoryStream.DefaultSmallChunkSize)]
55-
[InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 1.5))]
56-
[InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 4)]
57-
[InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 5.5))]
58-
[InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 16)]
64+
[InlineData(DefaultSmallChunkSize)]
65+
[InlineData((int)(DefaultSmallChunkSize * 1.5))]
66+
[InlineData(DefaultSmallChunkSize * 4)]
67+
[InlineData((int)(DefaultSmallChunkSize * 5.5))]
68+
[InlineData(DefaultSmallChunkSize * 16)]
5969
public void MemoryStream_ReadByteTest(int length)
6070
{
6171
using MemoryStream ms = this.CreateTestStream(length);
@@ -72,11 +82,11 @@ public void MemoryStream_ReadByteTest(int length)
7282
}
7383

7484
[Theory]
75-
[InlineData(ChunkedMemoryStream.DefaultSmallChunkSize)]
76-
[InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 1.5))]
77-
[InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 4)]
78-
[InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 5.5))]
79-
[InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 16)]
85+
[InlineData(DefaultSmallChunkSize)]
86+
[InlineData((int)(DefaultSmallChunkSize * 1.5))]
87+
[InlineData(DefaultSmallChunkSize * 4)]
88+
[InlineData((int)(DefaultSmallChunkSize * 5.5))]
89+
[InlineData(DefaultSmallChunkSize * 16)]
8090
public void MemoryStream_ReadByteBufferTest(int length)
8191
{
8292
using MemoryStream ms = this.CreateTestStream(length);
@@ -95,11 +105,11 @@ public void MemoryStream_ReadByteBufferTest(int length)
95105
}
96106

97107
[Theory]
98-
[InlineData(ChunkedMemoryStream.DefaultSmallChunkSize)]
99-
[InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 1.5))]
100-
[InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 4)]
101-
[InlineData((int)(ChunkedMemoryStream.DefaultSmallChunkSize * 5.5))]
102-
[InlineData(ChunkedMemoryStream.DefaultSmallChunkSize * 16)]
108+
[InlineData(DefaultSmallChunkSize)]
109+
[InlineData((int)(DefaultSmallChunkSize * 1.5))]
110+
[InlineData(DefaultSmallChunkSize * 4)]
111+
[InlineData((int)(DefaultSmallChunkSize * 5.5))]
112+
[InlineData(DefaultSmallChunkSize * 16)]
103113
public void MemoryStream_ReadByteBufferSpanTest(int length)
104114
{
105115
using MemoryStream ms = this.CreateTestStream(length);

0 commit comments

Comments
 (0)