Skip to content

Commit 2e1063e

Browse files
authored
Remove slabs from the slab memory pool (#30732)
- Slabs add an extra complication when trying to free blocks (blocks are attached to slabs and would need to be ref counted). Instead, since we can now directly allocate on the POH, slabs are less meaningful. - Use 4K arrays on the pinned heap - Remove the finalizer from MemoryPoolBlock
1 parent 42394df commit 2e1063e

File tree

3 files changed

+9
-178
lines changed

3 files changed

+9
-178
lines changed

src/Shared/Buffers.MemoryPool/MemoryPoolBlock.cs

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,52 +6,29 @@
66
namespace System.Buffers
77
{
88
/// <summary>
9-
/// Block tracking object used by the byte buffer memory pool. A slab is a large allocation which is divided into smaller blocks. The
10-
/// individual blocks are then treated as independent array segments.
9+
/// Wraps an array allocated in the pinned object heap in a reusable block of managed memory
1110
/// </summary>
1211
internal sealed class MemoryPoolBlock : IMemoryOwner<byte>
1312
{
14-
private readonly int _offset;
15-
private readonly int _length;
16-
17-
/// <summary>
18-
/// This object cannot be instantiated outside of the static Create method
19-
/// </summary>
20-
internal MemoryPoolBlock(SlabMemoryPool pool, MemoryPoolSlab slab, int offset, int length)
13+
internal MemoryPoolBlock(SlabMemoryPool pool, int length)
2114
{
22-
_offset = offset;
23-
_length = length;
24-
2515
Pool = pool;
26-
Slab = slab;
2716

28-
Memory = MemoryMarshal.CreateFromPinnedArray(slab.PinnedArray, _offset, _length);
17+
var pinnedArray = GC.AllocateUninitializedArray<byte>(length, pinned: true);
18+
19+
Memory = MemoryMarshal.CreateFromPinnedArray(pinnedArray, 0, pinnedArray.Length);
2920
}
3021

3122
/// <summary>
3223
/// Back-reference to the memory pool which this block was allocated from. It may only be returned to this pool.
3324
/// </summary>
3425
public SlabMemoryPool Pool { get; }
3526

36-
/// <summary>
37-
/// Back-reference to the slab from which this block was taken, or null if it is one-time-use memory.
38-
/// </summary>
39-
public MemoryPoolSlab Slab { get; }
40-
4127
public Memory<byte> Memory { get; }
4228

43-
~MemoryPoolBlock()
44-
{
45-
Pool.RefreshBlock(Slab, _offset, _length);
46-
}
47-
4829
public void Dispose()
4930
{
5031
Pool.Return(this);
5132
}
52-
53-
public void Lease()
54-
{
55-
}
5633
}
5734
}

src/Shared/Buffers.MemoryPool/MemoryPoolSlab.cs

Lines changed: 0 additions & 44 deletions
This file was deleted.

src/Shared/Buffers.MemoryPool/SlabMemoryPool.cs

Lines changed: 4 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

44
using System.Collections.Concurrent;
5-
using System.Diagnostics;
6-
using System.Runtime.InteropServices;
7-
using System.Threading;
85

96
#nullable enable
107

@@ -20,13 +17,6 @@ internal sealed class SlabMemoryPool : MemoryPool<byte>
2017
/// </summary>
2118
private const int _blockSize = 4096;
2219

23-
/// <summary>
24-
/// Allocating 32 contiguous blocks per slab makes the slab size 128k. This is larger than the 85k size which will place the memory
25-
/// in the large object heap. This means the GC will not try to relocate this array, so the fact it remains pinned does not negatively
26-
/// affect memory management's compactification.
27-
/// </summary>
28-
private const int _blockCount = 32;
29-
3020
/// <summary>
3121
/// Max allocation block size for pooled blocks,
3222
/// larger values can be leased but they will be disposed after use rather than returned to the pool.
@@ -38,30 +28,17 @@ internal sealed class SlabMemoryPool : MemoryPool<byte>
3828
/// </summary>
3929
public static int BlockSize => _blockSize;
4030

41-
/// <summary>
42-
/// 4096 * 32 gives you a slabLength of 128k contiguous bytes allocated per slab
43-
/// </summary>
44-
private static readonly int _slabLength = _blockSize * _blockCount;
45-
4631
/// <summary>
4732
/// Thread-safe collection of blocks which are currently in the pool. A slab will pre-allocate all of the block tracking objects
4833
/// and add them to this collection. When memory is requested it is taken from here first, and when it is returned it is re-added.
4934
/// </summary>
5035
private readonly ConcurrentQueue<MemoryPoolBlock> _blocks = new ConcurrentQueue<MemoryPoolBlock>();
5136

52-
/// <summary>
53-
/// Thread-safe collection of slabs which have been allocated by this pool. As long as a slab is in this collection and slab.IsActive,
54-
/// the blocks will be added to _blocks when returned.
55-
/// </summary>
56-
private readonly ConcurrentStack<MemoryPoolSlab> _slabs = new ConcurrentStack<MemoryPoolSlab>();
57-
5837
/// <summary>
5938
/// This is part of implementing the IDisposable pattern.
6039
/// </summary>
6140
private bool _isDisposed; // To detect redundant calls
6241

63-
private int _totalAllocatedBlocks;
64-
6542
private readonly object _disposeSync = new object();
6643

6744
/// <summary>
@@ -76,16 +53,6 @@ public override IMemoryOwner<byte> Rent(int size = AnySize)
7653
MemoryPoolThrowHelper.ThrowArgumentOutOfRangeException_BufferRequestTooLarge(_blockSize);
7754
}
7855

79-
var block = Lease();
80-
return block;
81-
}
82-
83-
/// <summary>
84-
/// Called to take a block from the pool.
85-
/// </summary>
86-
/// <returns>The block that is reserved for the called. It must be passed to Return when it is no longer being used.</returns>
87-
private MemoryPoolBlock Lease()
88-
{
8956
if (_isDisposed)
9057
{
9158
MemoryPoolThrowHelper.ThrowObjectDisposedException(MemoryPoolThrowHelper.ExceptionArgument.MemoryPool);
@@ -94,53 +61,9 @@ private MemoryPoolBlock Lease()
9461
if (_blocks.TryDequeue(out var block))
9562
{
9663
// block successfully taken from the stack - return it
97-
98-
block.Lease();
9964
return block;
10065
}
101-
// no blocks available - grow the pool
102-
block = AllocateSlab();
103-
block.Lease();
104-
return block;
105-
}
106-
107-
/// <summary>
108-
/// Internal method called when a block is requested and the pool is empty. It allocates one additional slab, creates all of the
109-
/// block tracking objects, and adds them all to the pool.
110-
/// </summary>
111-
private MemoryPoolBlock AllocateSlab()
112-
{
113-
var slab = MemoryPoolSlab.Create(_slabLength);
114-
_slabs.Push(slab);
115-
116-
// Get the address for alignment
117-
IntPtr basePtr = Marshal.UnsafeAddrOfPinnedArrayElement(slab.PinnedArray!, 0);
118-
// Page align the blocks
119-
var offset = (int)((((ulong)basePtr + (uint)_blockSize - 1) & ~((uint)_blockSize - 1)) - (ulong)basePtr);
120-
// Ensure page aligned
121-
Debug.Assert(((ulong)basePtr + (uint)offset) % _blockSize == 0);
122-
123-
var blockCount = (_slabLength - offset) / _blockSize;
124-
Interlocked.Add(ref _totalAllocatedBlocks, blockCount);
125-
126-
MemoryPoolBlock? block = null;
127-
128-
for (int i = 0; i < blockCount; i++)
129-
{
130-
block = new MemoryPoolBlock(this, slab, offset, _blockSize);
131-
132-
if (i != blockCount - 1) // last block
133-
{
134-
#if BLOCK_LEASE_TRACKING
135-
block.IsLeased = true;
136-
#endif
137-
Return(block);
138-
}
139-
140-
offset += _blockSize;
141-
}
142-
143-
return block!;
66+
return new MemoryPoolBlock(this, BlockSize);
14467
}
14568

14669
/// <summary>
@@ -163,25 +86,6 @@ internal void Return(MemoryPoolBlock block)
16386
{
16487
_blocks.Enqueue(block);
16588
}
166-
else
167-
{
168-
GC.SuppressFinalize(block);
169-
}
170-
}
171-
172-
// This method can ONLY be called from the finalizer of MemoryPoolBlock
173-
internal void RefreshBlock(MemoryPoolSlab slab, int offset, int length)
174-
{
175-
lock (_disposeSync)
176-
{
177-
if (!_isDisposed && slab != null && slab.IsActive)
178-
{
179-
// Need to make a new object because this one is being finalized
180-
// Note, this must be called within the _disposeSync lock because the block
181-
// could be disposed at the same time as the finalizer.
182-
Return(new MemoryPoolBlock(this, slab, offset, length));
183-
}
184-
}
18589
}
18690

18791
protected override void Dispose(bool disposing)
@@ -197,17 +101,11 @@ protected override void Dispose(bool disposing)
197101

198102
if (disposing)
199103
{
200-
while (_slabs.TryPop(out var slab))
104+
// Discard blocks in pool
105+
while (_blocks.TryDequeue(out _))
201106
{
202-
// dispose managed state (managed objects).
203-
slab.Dispose();
204-
}
205-
}
206107

207-
// Discard blocks in pool
208-
while (_blocks.TryDequeue(out var block))
209-
{
210-
GC.SuppressFinalize(block);
108+
}
211109
}
212110
}
213111
}

0 commit comments

Comments
 (0)