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

Commit 1ec965d

Browse files
committed
Partitioned memory pool
1 parent 542490f commit 1ec965d

File tree

4 files changed

+84
-27
lines changed

4 files changed

+84
-27
lines changed

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

Lines changed: 59 additions & 17 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,
@@ -55,9 +66,14 @@ public class MemoryPool2 : IDisposable
5566

5667
public MemoryPool2()
5768
{
58-
// Allocate on creation or multiple simultaneous connections
59-
// will all allocate rather than reuse the pooled buffers
60-
Return(AllocateSlab());
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+
}
6177
}
6278

6379
/// <summary>
@@ -66,36 +82,46 @@ public MemoryPool2()
6682
/// <param name="minimumSize">The block returned must be at least this size. It may be larger than this minimum size, and if so,
6783
/// the caller may write to the block's entire size rather than being limited to the minumumSize requested.</param>
6884
/// <returns>The block that is reserved for the called. It must be passed to Return when it is no longer being used.</returns>
69-
public MemoryPoolBlock2 Lease(int minimumSize)
85+
public MemoryPoolBlock2 Lease(int minimumSize = MaxPooledBlockLength)
7086
{
7187
if (minimumSize > _blockLength)
7288
{
7389
// The requested minimumSize is actually larger then the usable memory of a single block.
7490
// Because this is the degenerate case, a one-time-use byte[] array and tracking object are allocated.
7591
// When this block tracking object is returned it is not added to the pool - instead it will be
7692
// allowed to be garbage collected normally.
77-
return MemoryPoolBlock2.Create(
78-
new ArraySegment<byte>(new byte[minimumSize]),
79-
dataPtr: IntPtr.Zero,
80-
pool: null,
81-
slab: null);
93+
return MemoryPoolBlock2.Create(new ArraySegment<byte>(new byte[minimumSize]));
8294
}
8395

8496
MemoryPoolBlock2 block;
85-
if (_blocks.TryDequeue(out block))
97+
98+
var preferedPartition = Thread.CurrentThread.ManagedThreadId % _partitionCount;
99+
100+
if (_blocks[preferedPartition].TryDequeue(out block))
86101
{
87102
// block successfully taken from the stack - return it
88103
return block;
89104
}
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+
90116
// no blocks available - grow the pool
91-
return AllocateSlab();
117+
return AllocateSlab(preferedPartition);
92118
}
93119

94120
/// <summary>
95121
/// Internal method called when a block is requested and the pool is empty. It allocates one additional slab, creates all of the
96122
/// block tracking objects, and adds them all to the pool.
97123
/// </summary>
98-
private MemoryPoolBlock2 AllocateSlab()
124+
private MemoryPoolBlock2 AllocateSlab(int partition)
99125
{
100126
var slab = MemoryPoolSlab2.Create(_slabLength);
101127
_slabs.Push(slab);
@@ -114,7 +140,8 @@ private MemoryPoolBlock2 AllocateSlab()
114140
new ArraySegment<byte>(slab.Array, offset, _blockLength),
115141
basePtr,
116142
this,
117-
slab);
143+
slab,
144+
partition);
118145
Return(block);
119146
}
120147

@@ -123,7 +150,8 @@ private MemoryPoolBlock2 AllocateSlab()
123150
new ArraySegment<byte>(slab.Array, offset, _blockLength),
124151
basePtr,
125152
this,
126-
slab);
153+
slab,
154+
partition);
127155

128156
return newBlock;
129157
}
@@ -138,8 +166,22 @@ private MemoryPoolBlock2 AllocateSlab()
138166
/// <param name="block">The block to return. It must have been acquired by calling Lease on the same memory pool instance.</param>
139167
public void Return(MemoryPoolBlock2 block)
140168
{
141-
block.Reset();
142-
_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+
}
143185
}
144186

145187
protected virtual void Dispose(bool disposing)

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

Lines changed: 17 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
@@ -89,10 +94,13 @@ protected MemoryPoolBlock2()
8994
{
9095
Pool.Return(new MemoryPoolBlock2
9196
{
92-
_dataArrayPtr = _dataArrayPtr,
9397
Data = Data,
98+
_dataArrayPtr = _dataArrayPtr,
9499
Pool = Pool,
95100
Slab = Slab,
101+
Start = Start,
102+
End = End,
103+
Partition = Partition
96104
});
97105
}
98106
}
@@ -129,11 +137,17 @@ public void Unpin()
129137
}
130138
}
131139

140+
public static MemoryPoolBlock2 Create(ArraySegment<byte> data)
141+
{
142+
return Create(data, IntPtr.Zero, null, null, -1);
143+
}
144+
132145
public static MemoryPoolBlock2 Create(
133146
ArraySegment<byte> data,
134147
IntPtr dataPtr,
135148
MemoryPool2 pool,
136-
MemoryPoolSlab2 slab)
149+
MemoryPoolSlab2 slab,
150+
int partition)
137151
{
138152
return new MemoryPoolBlock2
139153
{
@@ -143,6 +157,7 @@ public static MemoryPoolBlock2 Create(
143157
Slab = slab,
144158
Start = data.Offset,
145159
End = data.Offset,
160+
Partition = partition
146161
};
147162
}
148163

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)