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

Commit bd31f24

Browse files
committed
Partitioned memory pool
1 parent efec0fe commit bd31f24

File tree

7 files changed

+119
-28
lines changed

7 files changed

+119
-28
lines changed

src/Microsoft.AspNet.Server.Kestrel/Http/ListenerContext.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,11 @@ public class ListenerContext : ServiceContext
1010
{
1111
public ListenerContext()
1212
{
13-
Memory2 = new MemoryPool2();
1413
}
1514

1615
public ListenerContext(ServiceContext serviceContext)
1716
: base(serviceContext)
1817
{
19-
Memory2 = new MemoryPool2();
2018
}
2119

2220
public ListenerContext(ListenerContext listenerContext)
@@ -25,7 +23,6 @@ public ListenerContext(ListenerContext listenerContext)
2523
ServerAddress = listenerContext.ServerAddress;
2624
Thread = listenerContext.Thread;
2725
Application = listenerContext.Application;
28-
Memory2 = listenerContext.Memory2;
2926
Log = listenerContext.Log;
3027
}
3128

@@ -35,6 +32,6 @@ public ListenerContext(ListenerContext listenerContext)
3532

3633
public RequestDelegate Application { get; set; }
3734

38-
public MemoryPool2 Memory2 { get; set; }
35+
public MemoryPool2 Memory2 => Thread.Memory2;
3936
}
4037
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public KestrelThread(KestrelEngine engine)
4242
_loop = new UvLoopHandle(_log);
4343
_post = new UvAsyncHandle(_log);
4444
_thread = new Thread(ThreadStart);
45+
Memory2 = new MemoryPool2();
4546
QueueCloseHandle = PostCloseHandle;
4647
}
4748

@@ -50,6 +51,8 @@ public KestrelThread(KestrelEngine engine)
5051

5152
public Action<Action<IntPtr>, IntPtr> QueueCloseHandle { get; internal set; }
5253

54+
public MemoryPool2 Memory2 { get; }
55+
5356
public Task StartAsync()
5457
{
5558
var tcs = new TaskCompletionSource<int>();

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

Lines changed: 69 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,70 @@ 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+
for (var i = 0; i < _blocks.Length; i++)
71+
{
72+
_blocks[i] = new ConcurrentQueue<MemoryPoolBlock2>();
73+
}
74+
}
75+
76+
public void PopulatePools()
77+
{
78+
for (var i = 0; i < _blocks.Length; i++)
79+
{
80+
// Allocate the inital set
81+
Return(AllocateSlab(i));
82+
}
83+
}
84+
5685
/// <summary>
5786
/// Called to take a block from the pool.
5887
/// </summary>
5988
/// <param name="minimumSize">The block returned must be at least this size. It may be larger than this minimum size, and if so,
6089
/// the caller may write to the block's entire size rather than being limited to the minumumSize requested.</param>
6190
/// <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)
91+
public MemoryPoolBlock2 Lease(int minimumSize = MaxPooledBlockLength)
6392
{
6493
if (minimumSize > _blockLength)
6594
{
6695
// The requested minimumSize is actually larger then the usable memory of a single block.
6796
// Because this is the degenerate case, a one-time-use byte[] array and tracking object are allocated.
6897
// When this block tracking object is returned it is not added to the pool - instead it will be
6998
// 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);
99+
return MemoryPoolBlock2.Create(new ArraySegment<byte>(new byte[minimumSize]));
75100
}
76101

77102
MemoryPoolBlock2 block;
78-
if (_blocks.TryDequeue(out block))
103+
104+
var preferedPartition = Thread.CurrentThread.ManagedThreadId % _partitionCount;
105+
106+
if (_blocks[preferedPartition].TryDequeue(out block))
79107
{
80108
// block successfully taken from the stack - return it
81109
return block;
82110
}
111+
112+
// no block, steal block from another parition
113+
for (var i = 1; i < _partitionCount; i++)
114+
{
115+
if (_blocks[(preferedPartition + i) % _partitionCount].TryDequeue(out block))
116+
{
117+
// block successfully taken from the stack - return it
118+
return block;
119+
}
120+
}
121+
83122
// no blocks available - grow the pool
84-
return AllocateSlab();
123+
return AllocateSlab(preferedPartition);
85124
}
86125

87126
/// <summary>
88127
/// Internal method called when a block is requested and the pool is empty. It allocates one additional slab, creates all of the
89128
/// block tracking objects, and adds them all to the pool.
90129
/// </summary>
91-
private MemoryPoolBlock2 AllocateSlab()
130+
private MemoryPoolBlock2 AllocateSlab(int partition)
92131
{
93132
var slab = MemoryPoolSlab2.Create(_slabLength);
94133
_slabs.Push(slab);
@@ -107,7 +146,8 @@ private MemoryPoolBlock2 AllocateSlab()
107146
new ArraySegment<byte>(slab.Array, offset, _blockLength),
108147
basePtr,
109148
this,
110-
slab);
149+
slab,
150+
partition);
111151
Return(block);
112152
}
113153

@@ -116,7 +156,8 @@ private MemoryPoolBlock2 AllocateSlab()
116156
new ArraySegment<byte>(slab.Array, offset, _blockLength),
117157
basePtr,
118158
this,
119-
slab);
159+
slab,
160+
partition);
120161

121162
return newBlock;
122163
}
@@ -131,8 +172,22 @@ private MemoryPoolBlock2 AllocateSlab()
131172
/// <param name="block">The block to return. It must have been acquired by calling Lease on the same memory pool instance.</param>
132173
public void Return(MemoryPoolBlock2 block)
133174
{
134-
block.Reset();
135-
_blocks.Enqueue(block);
175+
var owningPool = block.Pool;
176+
if (owningPool == null)
177+
{
178+
// not pooled block, throw away
179+
return;
180+
}
181+
if (owningPool != this)
182+
{
183+
throw new InvalidOperationException("Returning " + nameof(MemoryPoolBlock2) + " to incorrect pool.");
184+
}
185+
if (owningPool == this)
186+
{
187+
block.Reset();
188+
// return to owning parition
189+
_blocks[block.Partition].Enqueue(block);
190+
}
136191
}
137192

138193
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: 19 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,24 @@ public void Start(RequestDelegate requestDelegate)
106107
}
107108

108109
engine.Start(threadCount);
110+
111+
for (var i = 0; i < engine.Threads.Count; i++)
112+
{
113+
engine.Threads[i].Memory2.PopulatePools();
114+
}
115+
116+
// Move all MemoryPoolBlock2 into Gen2
117+
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
118+
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true);
119+
GC.WaitForPendingFinalizers();
120+
121+
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
122+
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true);
123+
GC.WaitForPendingFinalizers();
124+
125+
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
126+
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true);
127+
109128
var atLeastOneListener = false;
110129

111130
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)