Skip to content
This repository was archived by the owner on Dec 18, 2018. It is now read-only.

Commit c0e50e2

Browse files
committed
Partitioned memory pool
1 parent efec0fe commit c0e50e2

File tree

5 files changed

+104
-24
lines changed

5 files changed

+104
-24
lines changed

src/Microsoft.AspNet.Server.Kestrel/Infrastructure/MemoryPool2.cs

Lines changed: 63 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Concurrent;
3+
using System.Threading;
34

45
namespace Microsoft.AspNet.Server.Kestrel.Infrastructure
56
{
@@ -36,11 +37,21 @@ public class MemoryPool2 : IDisposable
3637
/// </summary>
3738
private const int _slabLength = _blockStride * _blockCount;
3839

40+
/// <summary>
41+
/// Max allocation block size for pooled blocks,
42+
/// larger values can be leased but they will be disposed after use rather than returned to the pool.
43+
/// </summary>
44+
public const int MaxPooledBlockLength = _blockLength;
45+
46+
// Processor count is a sys call https://github.com/dotnet/coreclr/blob/0e0ff9d17ab586f3cc7224dd33d8781cd77f3ca8/src/mscorlib/src/System/Environment.cs#L548
47+
// Nor does it look like its constant https://github.com/dotnet/corefx/blob/master/src/System.Threading.Tasks.Parallel/src/System/Threading/PlatformHelper.cs#L23
48+
public static int _partitionCount = Environment.ProcessorCount;
49+
3950
/// <summary>
4051
/// Thread-safe collection of blocks which are currently in the pool. A slab will pre-allocate all of the block tracking objects
4152
/// 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.
4253
/// </summary>
43-
private readonly ConcurrentQueue<MemoryPoolBlock2> _blocks = new ConcurrentQueue<MemoryPoolBlock2>();
54+
private readonly ConcurrentQueue<MemoryPoolBlock2>[] _blocks;
4455

4556
/// <summary>
4657
/// Thread-safe collection of slabs which have been allocated by this pool. As long as a slab is in this collection and slab.IsActive,
@@ -53,42 +64,64 @@ public class MemoryPool2 : IDisposable
5364
/// </summary>
5465
private bool _disposedValue = false; // To detect redundant calls
5566

67+
public MemoryPool2()
68+
{
69+
_blocks = new ConcurrentQueue<MemoryPoolBlock2>[_partitionCount];
70+
71+
for (var i = 0; i < _blocks.Length; i++)
72+
{
73+
_blocks[i] = new ConcurrentQueue<MemoryPoolBlock2>();
74+
// Allocate the inital set
75+
Return(AllocateSlab(i));
76+
}
77+
}
78+
5679
/// <summary>
5780
/// Called to take a block from the pool.
5881
/// </summary>
5982
/// <param name="minimumSize">The block returned must be at least this size. It may be larger than this minimum size, and if so,
6083
/// the caller may write to the block's entire size rather than being limited to the minumumSize requested.</param>
6184
/// <returns>The block that is reserved for the called. It must be passed to Return when it is no longer being used.</returns>
62-
public MemoryPoolBlock2 Lease(int minimumSize)
85+
public MemoryPoolBlock2 Lease(int minimumSize = MaxPooledBlockLength)
6386
{
6487
if (minimumSize > _blockLength)
6588
{
6689
// The requested minimumSize is actually larger then the usable memory of a single block.
6790
// Because this is the degenerate case, a one-time-use byte[] array and tracking object are allocated.
6891
// When this block tracking object is returned it is not added to the pool - instead it will be
6992
// allowed to be garbage collected normally.
70-
return MemoryPoolBlock2.Create(
71-
new ArraySegment<byte>(new byte[minimumSize]),
72-
dataPtr: IntPtr.Zero,
73-
pool: null,
74-
slab: null);
93+
return MemoryPoolBlock2.Create(new ArraySegment<byte>(new byte[minimumSize]));
7594
}
7695

7796
MemoryPoolBlock2 block;
78-
if (_blocks.TryDequeue(out block))
97+
98+
var preferedPartition = Thread.CurrentThread.ManagedThreadId % _partitionCount;
99+
100+
if (_blocks[preferedPartition].TryDequeue(out block))
79101
{
80102
// block successfully taken from the stack - return it
81103
return block;
82104
}
105+
106+
// no block, steal block from another parition
107+
for (var i = 1; i < _partitionCount; i++)
108+
{
109+
if (_blocks[(preferedPartition + i) % _partitionCount].TryDequeue(out block))
110+
{
111+
// block successfully taken from the stack - return it
112+
return block;
113+
}
114+
}
115+
83116
// no blocks available - grow the pool
84-
return AllocateSlab();
117+
return AllocateSlab(preferedPartition);
85118
}
86119

87120
/// <summary>
88121
/// Internal method called when a block is requested and the pool is empty. It allocates one additional slab, creates all of the
89122
/// block tracking objects, and adds them all to the pool.
90123
/// </summary>
91-
private MemoryPoolBlock2 AllocateSlab()
124+
private MemoryPoolBlock2 AllocateSlab(int partition)
92125
{
93126
var slab = MemoryPoolSlab2.Create(_slabLength);
94127
_slabs.Push(slab);
@@ -107,7 +140,8 @@ private MemoryPoolBlock2 AllocateSlab()
107140
new ArraySegment<byte>(slab.Array, offset, _blockLength),
108141
basePtr,
109142
this,
110-
slab);
143+
slab,
144+
partition);
111145
Return(block);
112146
}
113147

@@ -116,7 +150,8 @@ private MemoryPoolBlock2 AllocateSlab()
116150
new ArraySegment<byte>(slab.Array, offset, _blockLength),
117151
basePtr,
118152
this,
119-
slab);
153+
slab,
154+
partition);
120155

121156
return newBlock;
122157
}
@@ -131,8 +166,22 @@ private MemoryPoolBlock2 AllocateSlab()
131166
/// <param name="block">The block to return. It must have been acquired by calling Lease on the same memory pool instance.</param>
132167
public void Return(MemoryPoolBlock2 block)
133168
{
134-
block.Reset();
135-
_blocks.Enqueue(block);
169+
var owningPool = block.Pool;
170+
if (owningPool == null)
171+
{
172+
// not pooled block, throw away
173+
return;
174+
}
175+
if (owningPool != this)
176+
{
177+
throw new InvalidOperationException("Returning " + nameof(MemoryPoolBlock2) + " to incorrect pool.");
178+
}
179+
if (owningPool == this)
180+
{
181+
block.Reset();
182+
// return to owning parition
183+
_blocks[block.Partition].Enqueue(block);
184+
}
136185
}
137186

138187
protected virtual void Dispose(bool disposing)

src/Microsoft.AspNet.Server.Kestrel/Infrastructure/MemoryPoolBlock2.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ protected MemoryPoolBlock2()
6666
/// </summary>
6767
public int End { get; set; }
6868

69+
/// <summary>
70+
/// The Partition represents the memory pool partition the block belongs to.
71+
/// </summary>
72+
public int Partition { get; private set; }
73+
6974
/// <summary>
7075
/// Reference to the next block of data when the overall "active" bytes spans multiple blocks. At the point when the block is
7176
/// leased Next is guaranteed to be null. Start, End, and Next are used together in order to create a linked-list of discontiguous
@@ -74,6 +79,7 @@ protected MemoryPoolBlock2()
7479
/// </summary>
7580
public MemoryPoolBlock2 Next { get; set; }
7681

82+
#if DEBUG
7783
~MemoryPoolBlock2()
7884
{
7985
Debug.Assert(!_pinHandle.IsAllocated, "Ad-hoc memory block wasn't unpinned");
@@ -89,13 +95,17 @@ protected MemoryPoolBlock2()
8995
{
9096
Pool.Return(new MemoryPoolBlock2
9197
{
92-
_dataArrayPtr = _dataArrayPtr,
9398
Data = Data,
99+
_dataArrayPtr = _dataArrayPtr,
94100
Pool = Pool,
95101
Slab = Slab,
102+
Start = Start,
103+
End = End,
104+
Partition = Partition
96105
});
97106
}
98107
}
108+
#endif
99109

100110
/// <summary>
101111
/// Called to ensure that a block is pinned, and return the pointer to native memory just after
@@ -129,11 +139,17 @@ public void Unpin()
129139
}
130140
}
131141

142+
public static MemoryPoolBlock2 Create(ArraySegment<byte> data)
143+
{
144+
return Create(data, IntPtr.Zero, null, null, -1);
145+
}
146+
132147
public static MemoryPoolBlock2 Create(
133148
ArraySegment<byte> data,
134149
IntPtr dataPtr,
135150
MemoryPool2 pool,
136-
MemoryPoolSlab2 slab)
151+
MemoryPoolSlab2 slab,
152+
int partition)
137153
{
138154
return new MemoryPoolBlock2
139155
{
@@ -143,6 +159,7 @@ public static MemoryPoolBlock2 Create(
143159
Slab = slab,
144160
Start = data.Offset,
145161
End = data.Offset,
162+
Partition = partition
146163
};
147164
}
148165

src/Microsoft.AspNet.Server.Kestrel/KestrelServer.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Microsoft.AspNet.Http.Features;
1010
using Microsoft.AspNet.Server.Kestrel.Http;
1111
using Microsoft.Extensions.Logging;
12+
using System.Runtime;
1213

1314
namespace Microsoft.AspNet.Server.Kestrel
1415
{
@@ -106,6 +107,19 @@ public void Start(RequestDelegate requestDelegate)
106107
}
107108

108109
engine.Start(threadCount);
110+
111+
// Move all MemoryPoolBlock2 into Gen2
112+
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
113+
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true);
114+
GC.WaitForPendingFinalizers();
115+
116+
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
117+
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true);
118+
GC.WaitForPendingFinalizers();
119+
120+
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
121+
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true);
122+
109123
var atLeastOneListener = false;
110124

111125
foreach (var address in information.Addresses)

test/Microsoft.AspNet.Server.KestrelTests/AsciiDecoder.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ private void FullByteRangeSupported()
1515
{
1616
var byteRange = Enumerable.Range(0, 255).Select(x => (byte)x).ToArray();
1717

18-
var mem = MemoryPoolBlock2.Create(new ArraySegment<byte>(byteRange), IntPtr.Zero, null, null);
18+
var mem = MemoryPoolBlock2.Create(new ArraySegment<byte>(byteRange));
1919
mem.End = byteRange.Length;
2020

2121
var begin = mem.GetIterator();
@@ -44,10 +44,10 @@ private void MultiBlockProducesCorrectResults()
4444
.Concat(byteRange)
4545
.ToArray();
4646

47-
var mem0 = MemoryPoolBlock2.Create(new ArraySegment<byte>(byteRange), IntPtr.Zero, null, null);
48-
var mem1 = MemoryPoolBlock2.Create(new ArraySegment<byte>(byteRange), IntPtr.Zero, null, null);
49-
var mem2 = MemoryPoolBlock2.Create(new ArraySegment<byte>(byteRange), IntPtr.Zero, null, null);
50-
var mem3 = MemoryPoolBlock2.Create(new ArraySegment<byte>(byteRange), IntPtr.Zero, null, null);
47+
var mem0 = MemoryPoolBlock2.Create(new ArraySegment<byte>(byteRange));
48+
var mem1 = MemoryPoolBlock2.Create(new ArraySegment<byte>(byteRange));
49+
var mem2 = MemoryPoolBlock2.Create(new ArraySegment<byte>(byteRange));
50+
var mem3 = MemoryPoolBlock2.Create(new ArraySegment<byte>(byteRange));
5151
mem0.End = byteRange.Length;
5252
mem1.End = byteRange.Length;
5353
mem2.End = byteRange.Length;
@@ -79,8 +79,8 @@ private void HeapAllocationProducesCorrectResults()
7979
var byteRange = Enumerable.Range(0, 16384 + 64).Select(x => (byte)x).ToArray();
8080
var expectedByteRange = byteRange.Concat(byteRange).ToArray();
8181

82-
var mem0 = MemoryPoolBlock2.Create(new ArraySegment<byte>(byteRange), IntPtr.Zero, null, null);
83-
var mem1 = MemoryPoolBlock2.Create(new ArraySegment<byte>(byteRange), IntPtr.Zero, null, null);
82+
var mem0 = MemoryPoolBlock2.Create(new ArraySegment<byte>(byteRange));
83+
var mem1 = MemoryPoolBlock2.Create(new ArraySegment<byte>(byteRange));
8484
mem0.End = byteRange.Length;
8585
mem1.End = byteRange.Length;
8686

test/Microsoft.AspNet.Server.KestrelTests/UrlPathDecoder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ public void DecodeWithBoundary(string raw, int rawLength, string expect, int exp
124124
private MemoryPoolIterator2 BuildSample(string data)
125125
{
126126
var store = data.Select(c => (byte)c).ToArray();
127-
var mem = MemoryPoolBlock2.Create(new ArraySegment<byte>(store), IntPtr.Zero, null, null);
127+
var mem = MemoryPoolBlock2.Create(new ArraySegment<byte>(store));
128128
mem.End = store.Length;
129129

130130
return mem.GetIterator();

0 commit comments

Comments
 (0)