Skip to content

Commit 4755383

Browse files
Implement exponential large buffer allocation strategy
1 parent baf6c19 commit 4755383

File tree

3 files changed

+315
-50
lines changed

3 files changed

+315
-50
lines changed

UnitTests/Tests.cs

Lines changed: 206 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -36,49 +36,57 @@ namespace Microsoft.IO.UnitTests
3636
/// </summary>
3737
public abstract class BaseRecyclableMemoryStreamTests
3838
{
39-
private const int DefaultBlockSize = 16384;
40-
private const int DefaultLargeBufferMultiple = 1 << 20;
41-
private const int DefaultMaximumBufferSize = 8 * (1 << 20);
42-
private const string DefaultTag = "NUnit";
39+
protected const int DefaultBlockSize = 16384;
40+
protected const int DefaultLargeBufferMultiple = 1 << 20;
41+
protected const int DefaultMaximumBufferSize = 8 * (1 << 20);
42+
protected const string DefaultTag = "NUnit";
4343
private const int MemoryStreamDisposed = 2;
4444
private const int MemoryStreamDoubleDispose = 3;
4545

4646
private readonly Random random = new Random();
4747

4848
#region RecyclableMemoryManager Tests
49+
[Test]
50+
public virtual void RecyclableMemoryManagerUsingMultipleOrExponentialLargeBuffer()
51+
{
52+
var memMgr = this.GetMemoryManager();
53+
Assert.That(memMgr.UseMultipleLargeBuffer, Is.True);
54+
Assert.That(memMgr.UseExponentialLargeBuffer, Is.False);
55+
}
56+
4957
[Test]
5058
public void RecyclableMemoryManagerThrowsExceptionOnZeroBlockSize()
5159
{
52-
Assert.Throws<ArgumentOutOfRangeException>(() => new RecyclableMemoryStreamManager(0, 100, 200));
53-
Assert.Throws<ArgumentOutOfRangeException>(() => new RecyclableMemoryStreamManager(-1, 100, 200));
54-
Assert.DoesNotThrow(() => new RecyclableMemoryStreamManager(1, 100, 200));
60+
Assert.Throws<ArgumentOutOfRangeException>(() => new RecyclableMemoryStreamManager(0, 100, 200, this.useExponentialLargeBuffer));
61+
Assert.Throws<ArgumentOutOfRangeException>(() => new RecyclableMemoryStreamManager(-1, 100, 200, this.useExponentialLargeBuffer));
62+
Assert.DoesNotThrow(() => new RecyclableMemoryStreamManager(1, 100, 200, this.useExponentialLargeBuffer));
5563
}
5664

5765
[Test]
5866
public void RecyclableMemoryManagerThrowsExceptionOnZeroLargeBufferMultipleSize()
5967
{
60-
Assert.Throws<ArgumentOutOfRangeException>(() => new RecyclableMemoryStreamManager(100, 0, 200));
61-
Assert.Throws<ArgumentOutOfRangeException>(() => new RecyclableMemoryStreamManager(100, -1, 200));
62-
Assert.DoesNotThrow(() => new RecyclableMemoryStreamManager(100, 100, 200));
68+
Assert.Throws<ArgumentOutOfRangeException>(() => new RecyclableMemoryStreamManager(100, 0, 200, this.useExponentialLargeBuffer));
69+
Assert.Throws<ArgumentOutOfRangeException>(() => new RecyclableMemoryStreamManager(100, -1, 200, this.useExponentialLargeBuffer));
70+
Assert.DoesNotThrow(() => new RecyclableMemoryStreamManager(100, 100, 200, this.useExponentialLargeBuffer));
6371
}
6472

6573
[Test]
6674
public void RecyclableMemoryManagerThrowsExceptionOnMaximumBufferSizeLessThanBlockSize()
6775
{
68-
Assert.Throws<ArgumentOutOfRangeException>(() => new RecyclableMemoryStreamManager(100, 100, 99));
69-
Assert.DoesNotThrow(() => new RecyclableMemoryStreamManager(100, 100, 100));
76+
Assert.Throws<ArgumentOutOfRangeException>(() => new RecyclableMemoryStreamManager(100, 100, 99, this.useExponentialLargeBuffer));
77+
Assert.DoesNotThrow(() => new RecyclableMemoryStreamManager(100, 100, 100, this.useExponentialLargeBuffer));
7078
}
7179

7280
[Test]
73-
public void RecyclableMemoryManagerThrowsExceptionOnMaximumBufferNotMultipleOfLargeBufferMultiple()
81+
public virtual void RecyclableMemoryManagerThrowsExceptionOnMaximumBufferNotMultipleOrExponentialOfLargeBufferMultiple()
7482
{
75-
Assert.Throws<ArgumentException>(() => new RecyclableMemoryStreamManager(100, 1024, 2025));
76-
Assert.Throws<ArgumentException>(() => new RecyclableMemoryStreamManager(100, 1024, 2023));
77-
Assert.DoesNotThrow(() => new RecyclableMemoryStreamManager(100, 1024, 2048));
83+
Assert.Throws<ArgumentException>(() => new RecyclableMemoryStreamManager(100, 1024, 2025, this.useExponentialLargeBuffer));
84+
Assert.Throws<ArgumentException>(() => new RecyclableMemoryStreamManager(100, 1024, 2023, this.useExponentialLargeBuffer));
85+
Assert.DoesNotThrow(() => new RecyclableMemoryStreamManager(100, 1024, 2048, this.useExponentialLargeBuffer));
7886
}
7987

8088
[Test]
81-
public void GetLargeBufferAlwaysAMultipleOfMegabyteAndAtLeastAsMuchAsRequestedForLargeBuffer()
89+
public virtual void GetLargeBufferAlwaysAMultipleOrExponentialOfMegabyteAndAtLeastAsMuchAsRequestedForLargeBuffer()
8290
{
8391
const int step = 200000;
8492
const int start = 1;
@@ -95,15 +103,15 @@ public void GetLargeBufferAlwaysAMultipleOfMegabyteAndAtLeastAsMuchAsRequestedFo
95103
}
96104

97105
[Test]
98-
public void AllMultiplesUpToMaxCanBePooled()
106+
public virtual void AllMultiplesOrExponentialUpToMaxCanBePooled()
99107
{
100108
const int BlockSize = 100;
101109
const int LargeBufferMultiple = 1000;
102110
const int MaxBufferSize = 8000;
103111

104112
for (var size = LargeBufferMultiple; size <= MaxBufferSize; size += LargeBufferMultiple)
105113
{
106-
var memMgr = new RecyclableMemoryStreamManager(BlockSize, LargeBufferMultiple, MaxBufferSize)
114+
var memMgr = new RecyclableMemoryStreamManager(BlockSize, LargeBufferMultiple, MaxBufferSize, this.useExponentialLargeBuffer)
107115
{AggressiveBufferReturn = this.AggressiveBufferRelease};
108116
var buffer = memMgr.GetLargeBuffer(size, DefaultTag);
109117
Assert.That(memMgr.LargePoolFreeSize, Is.EqualTo(0));
@@ -173,7 +181,7 @@ public void ReturnBlocksWithInvalidBuffersThrowsException()
173181
}
174182

175183
[Test]
176-
public void RequestTooLargeBufferAdjustsInUseCounter()
184+
public virtual void RequestTooLargeBufferAdjustsInUseCounter()
177185
{
178186
var memMgr = this.GetMemoryManager();
179187
var buffer = memMgr.GetLargeBuffer(memMgr.MaximumBufferSize + 1, DefaultTag);
@@ -258,15 +266,15 @@ public void ReturningLargeBufferNeverDroppedIfMaxFreeSizeZero()
258266
this.TestDroppingLargeBuffer(0);
259267
}
260268

261-
private void TestDroppingLargeBuffer(long maxFreeLargeBufferSize)
269+
protected virtual void TestDroppingLargeBuffer(long maxFreeLargeBufferSize)
262270
{
263271
const int BlockSize = 100;
264272
const int LargeBufferMultiple = 1000;
265273
const int MaxBufferSize = 8000;
266274

267275
for (var size = LargeBufferMultiple; size <= MaxBufferSize; size += LargeBufferMultiple)
268276
{
269-
var memMgr = new RecyclableMemoryStreamManager(BlockSize, LargeBufferMultiple, MaxBufferSize)
277+
var memMgr = new RecyclableMemoryStreamManager(BlockSize, LargeBufferMultiple, MaxBufferSize, this.useExponentialLargeBuffer)
270278
{
271279
AggressiveBufferReturn = this.AggressiveBufferRelease,
272280
MaximumFreeLargePoolBytes = maxFreeLargeBufferSize
@@ -1639,7 +1647,7 @@ public void DisposeTwiceDoesNotThrowException()
16391647
public async Task ConcurrentDoubleDisposeSucceeds()
16401648
{
16411649
int blockSize = 10;
1642-
var manager = new RecyclableMemoryStreamManager(blockSize: blockSize, largeBufferMultiple: 20, maximumBufferSize: 100);
1650+
var manager = new RecyclableMemoryStreamManager(blockSize: blockSize, largeBufferMultiple: 20, maximumBufferSize: 160, useExponentialLargeBuffer: this.useExponentialLargeBuffer);
16431651
RecyclableMemoryStream recyclableMemoryStream = new RecyclableMemoryStream(manager, TestContext.CurrentContext.Test.Name);
16441652

16451653
Assert.AreEqual(0, manager.SmallBlocksFree, "Verify manager starts with no blocks free");
@@ -1898,10 +1906,10 @@ protected byte[] GetRandomBuffer(int length)
18981906
return buffer;
18991907
}
19001908

1901-
protected RecyclableMemoryStreamManager GetMemoryManager()
1909+
protected virtual RecyclableMemoryStreamManager GetMemoryManager()
19021910
{
19031911
return new RecyclableMemoryStreamManager(DefaultBlockSize, DefaultLargeBufferMultiple,
1904-
DefaultMaximumBufferSize)
1912+
DefaultMaximumBufferSize, this.useExponentialLargeBuffer)
19051913
{
19061914
AggressiveBufferReturn = this.AggressiveBufferRelease,
19071915
};
@@ -1919,6 +1927,11 @@ private RecyclableMemoryStream GetRandomStream()
19191927

19201928
protected abstract bool AggressiveBufferRelease { get; }
19211929

1930+
protected virtual bool useExponentialLargeBuffer
1931+
{
1932+
get { return false; }
1933+
}
1934+
19221935
/*
19231936
* TODO: clocke to release logging libraries to enable some tests.
19241937
[TestFixtureSetUp]
@@ -2034,4 +2047,172 @@ protected override bool AggressiveBufferRelease
20342047
get { return true; }
20352048
}
20362049
}
2050+
2051+
public abstract class BaseRecyclableMemoryStreamTestsUsingExponentialLargeBuffer : BaseRecyclableMemoryStreamTests
2052+
{
2053+
protected override bool useExponentialLargeBuffer
2054+
{
2055+
get { return true; }
2056+
}
2057+
2058+
[Test]
2059+
public override void RecyclableMemoryManagerUsingMultipleOrExponentialLargeBuffer()
2060+
{
2061+
var memMgr = this.GetMemoryManager();
2062+
Assert.That(memMgr.UseMultipleLargeBuffer, Is.False);
2063+
Assert.That(memMgr.UseExponentialLargeBuffer, Is.True);
2064+
}
2065+
2066+
[Test]
2067+
public override void RecyclableMemoryManagerThrowsExceptionOnMaximumBufferNotMultipleOrExponentialOfLargeBufferMultiple()
2068+
{
2069+
Assert.Throws<ArgumentException>(() => new RecyclableMemoryStreamManager(100, 1024, 2025, this.useExponentialLargeBuffer));
2070+
Assert.Throws<ArgumentException>(() => new RecyclableMemoryStreamManager(100, 1024, 2023, this.useExponentialLargeBuffer));
2071+
Assert.Throws<ArgumentException>(() => new RecyclableMemoryStreamManager(100, 1024, 3072, this.useExponentialLargeBuffer));
2072+
Assert.DoesNotThrow(() => new RecyclableMemoryStreamManager(100, 1024, 2048, this.useExponentialLargeBuffer));
2073+
Assert.DoesNotThrow(() => new RecyclableMemoryStreamManager(100, 1024, 4096, this.useExponentialLargeBuffer));
2074+
}
2075+
2076+
[Test]
2077+
public override void GetLargeBufferAlwaysAMultipleOrExponentialOfMegabyteAndAtLeastAsMuchAsRequestedForLargeBuffer()
2078+
{
2079+
const int step = 200000;
2080+
const int start = 1;
2081+
const int end = 16000000;
2082+
var memMgr = this.GetMemoryManager();
2083+
2084+
for (var i = start; i <= end; i += step)
2085+
{
2086+
var buffer = memMgr.GetLargeBuffer(i, DefaultTag);
2087+
Assert.That(buffer.Length >= i, Is.True);
2088+
Assert.That(memMgr.LargeBufferMultiple * (int)Math.Pow(2, Math.Floor(Math.Log(buffer.Length / memMgr.LargeBufferMultiple, 2))) == buffer.Length, Is.True,
2089+
"buffer length of {0} is not a exponential of {1}", buffer.Length, memMgr.LargeBufferMultiple);
2090+
}
2091+
}
2092+
2093+
[Test]
2094+
public override void AllMultiplesOrExponentialUpToMaxCanBePooled()
2095+
{
2096+
const int BlockSize = 100;
2097+
const int LargeBufferMultiple = 1000;
2098+
const int MaxBufferSize = 8000;
2099+
2100+
for (var size = LargeBufferMultiple; size <= MaxBufferSize; size *= 2)
2101+
{
2102+
var memMgr = new RecyclableMemoryStreamManager(BlockSize, LargeBufferMultiple, MaxBufferSize, this.useExponentialLargeBuffer)
2103+
{ AggressiveBufferReturn = this.AggressiveBufferRelease };
2104+
var buffer = memMgr.GetLargeBuffer(size, DefaultTag);
2105+
Assert.That(memMgr.LargePoolFreeSize, Is.EqualTo(0));
2106+
Assert.That(memMgr.LargePoolInUseSize, Is.EqualTo(size));
2107+
2108+
memMgr.ReturnLargeBuffer(buffer, DefaultTag);
2109+
2110+
Assert.That(memMgr.LargePoolFreeSize, Is.EqualTo(size));
2111+
Assert.That(memMgr.LargePoolInUseSize, Is.EqualTo(0));
2112+
}
2113+
}
2114+
2115+
[Test]
2116+
public override void RequestTooLargeBufferAdjustsInUseCounter()
2117+
{
2118+
var memMgr = this.GetMemoryManager();
2119+
var buffer = memMgr.GetLargeBuffer(memMgr.MaximumBufferSize + 1, DefaultTag);
2120+
Assert.That(buffer.Length, Is.EqualTo(memMgr.MaximumBufferSize * 2));
2121+
Assert.That(memMgr.LargePoolInUseSize, Is.EqualTo(buffer.Length));
2122+
}
2123+
2124+
protected override void TestDroppingLargeBuffer(long maxFreeLargeBufferSize)
2125+
{
2126+
const int BlockSize = 100;
2127+
const int LargeBufferMultiple = 1000;
2128+
const int MaxBufferSize = 8000;
2129+
2130+
for (var size = LargeBufferMultiple; size <= MaxBufferSize; size *= 2)
2131+
{
2132+
var memMgr = new RecyclableMemoryStreamManager(BlockSize, LargeBufferMultiple, MaxBufferSize, this.useExponentialLargeBuffer)
2133+
{
2134+
AggressiveBufferReturn = this.AggressiveBufferRelease,
2135+
MaximumFreeLargePoolBytes = maxFreeLargeBufferSize
2136+
};
2137+
2138+
var buffers = new List<byte[]>();
2139+
2140+
//Get one extra buffer
2141+
var buffersToRetrieve = (maxFreeLargeBufferSize > 0) ? (maxFreeLargeBufferSize / size + 1) : 10;
2142+
for (var i = 0; i < buffersToRetrieve; i++)
2143+
{
2144+
buffers.Add(memMgr.GetLargeBuffer(size, DefaultTag));
2145+
}
2146+
Assert.That(memMgr.LargePoolInUseSize, Is.EqualTo(size * buffersToRetrieve));
2147+
Assert.That(memMgr.LargePoolFreeSize, Is.EqualTo(0));
2148+
foreach (var buffer in buffers)
2149+
{
2150+
memMgr.ReturnLargeBuffer(buffer, DefaultTag);
2151+
}
2152+
Assert.That(memMgr.LargePoolInUseSize, Is.EqualTo(0));
2153+
if (maxFreeLargeBufferSize > 0)
2154+
{
2155+
Assert.That(memMgr.LargePoolFreeSize, Is.LessThanOrEqualTo(maxFreeLargeBufferSize));
2156+
}
2157+
else
2158+
{
2159+
Assert.That(memMgr.LargePoolFreeSize, Is.EqualTo(buffersToRetrieve * size));
2160+
}
2161+
}
2162+
}
2163+
}
2164+
2165+
[TestFixture]
2166+
public sealed class RecyclableMemoryStreamTestsWithPassiveBufferReleaseUsingExponentialLargeBuffer : BaseRecyclableMemoryStreamTestsUsingExponentialLargeBuffer
2167+
{
2168+
protected override bool AggressiveBufferRelease
2169+
{
2170+
get { return false; }
2171+
}
2172+
2173+
[Test]
2174+
public void OldBuffersAreKeptInStreamUntilDispose()
2175+
{
2176+
var stream = this.GetDefaultStream();
2177+
var memMgr = stream.MemoryManager;
2178+
var buffer = this.GetRandomBuffer(stream.MemoryManager.LargeBufferMultiple);
2179+
stream.Write(buffer, 0, buffer.Length);
2180+
stream.GetBuffer();
2181+
2182+
Assert.That(memMgr.LargePoolInUseSize, Is.EqualTo(memMgr.LargeBufferMultiple * (1)));
2183+
Assert.That(memMgr.LargePoolFreeSize, Is.EqualTo(0));
2184+
Assert.That(memMgr.SmallPoolFreeSize, Is.EqualTo(0));
2185+
Assert.That(memMgr.SmallPoolInUseSize, Is.EqualTo(memMgr.LargeBufferMultiple));
2186+
2187+
stream.Write(buffer, 0, buffer.Length);
2188+
2189+
Assert.That(memMgr.LargePoolFreeSize, Is.EqualTo(0));
2190+
Assert.That(memMgr.LargePoolInUseSize, Is.EqualTo(memMgr.LargeBufferMultiple * (1 + 2)));
2191+
Assert.That(memMgr.SmallPoolFreeSize, Is.EqualTo(0));
2192+
Assert.That(memMgr.SmallPoolInUseSize, Is.EqualTo(memMgr.LargeBufferMultiple));
2193+
2194+
stream.Write(buffer, 0, buffer.Length);
2195+
2196+
Assert.That(memMgr.LargePoolFreeSize, Is.EqualTo(0));
2197+
Assert.That(memMgr.LargePoolInUseSize, Is.EqualTo(memMgr.LargeBufferMultiple * (1 + 2 + 4)));
2198+
Assert.That(memMgr.SmallPoolFreeSize, Is.EqualTo(0));
2199+
Assert.That(memMgr.SmallPoolInUseSize, Is.EqualTo(memMgr.LargeBufferMultiple));
2200+
2201+
stream.Dispose();
2202+
2203+
Assert.That(memMgr.LargePoolFreeSize, Is.EqualTo(memMgr.LargeBufferMultiple * (1 + 2 + 4)));
2204+
Assert.That(memMgr.LargePoolInUseSize, Is.EqualTo(0));
2205+
Assert.That(memMgr.SmallPoolFreeSize, Is.EqualTo(memMgr.LargeBufferMultiple));
2206+
Assert.That(memMgr.SmallPoolInUseSize, Is.EqualTo(0));
2207+
}
2208+
}
2209+
2210+
[TestFixture]
2211+
public sealed class RecyclableMemoryStreamTestsWithAggressiveBufferReleaseUsingExponentialLargeBuffer : BaseRecyclableMemoryStreamTestsUsingExponentialLargeBuffer
2212+
{
2213+
protected override bool AggressiveBufferRelease
2214+
{
2215+
get { return true; }
2216+
}
2217+
}
20372218
}

src/RecyclableMemoryStream.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ namespace Microsoft.IO
3333
/// buffers.
3434
/// </summary>
3535
/// <remarks>
36-
/// This class works in tandem with the RecylableMemoryStreamManager to supply MemoryStream
36+
/// This class works in tandem with the RecyclableMemoryStreamManager to supply MemoryStream
3737
/// objects to callers, while avoiding these specific problems:
3838
/// 1. LOH allocations - since all large buffers are pooled, they will never incur a Gen2 GC
3939
/// 2. Memory waste - A standard memory stream doubles its size when it runs out of room. This
@@ -50,7 +50,7 @@ namespace Microsoft.IO
5050
/// The biggest wrinkle in this implementation is when GetBuffer() is called. This requires a single
5151
/// contiguous buffer. If only a single block is in use, then that block is returned. If multiple blocks
5252
/// are in use, we retrieve a larger buffer from the memory manager. These large buffers are also pooled,
53-
/// split by size--they are multiples of a chunk size (1 MB by default).
53+
/// split by size--they are multiples/exponentials of a chunk size (1 MB by default).
5454
///
5555
/// Once a large buffer is assigned to the stream the blocks are NEVER again used for this stream. All operations take place on the
5656
/// large buffer. The large buffer can be replaced by a larger buffer from the pool as needed. All blocks and large buffers

0 commit comments

Comments
 (0)