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

Commit 528b13a

Browse files
committed
Request Header String Pool
1 parent 1c320d7 commit 528b13a

File tree

6 files changed

+224
-41
lines changed

6 files changed

+224
-41
lines changed

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ public abstract partial class Frame : FrameContext, IFrameControl
7070
private readonly Action<IFeatureCollection> _prepareRequest;
7171

7272
private readonly string _pathBase;
73+
protected readonly StringPool _stringPool = new StringPool();
7374

7475
public Frame(ConnectionContext context)
7576
: this(context, remoteEndPoint: null, localEndPoint: null, prepareRequest: null)
@@ -645,7 +646,7 @@ protected bool TakeStartLine(SocketInput input)
645646
{
646647
return false;
647648
}
648-
var method = begin.GetAsciiString(scan);
649+
var method = begin.GetAsciiString(scan, _stringPool);
649650

650651
scan.Take();
651652
begin = scan;
@@ -669,7 +670,7 @@ protected bool TakeStartLine(SocketInput input)
669670
{
670671
return false;
671672
}
672-
queryString = begin.GetAsciiString(scan);
673+
queryString = begin.GetAsciiString(scan, _stringPool);
673674
}
674675

675676
scan.Take();
@@ -678,7 +679,7 @@ protected bool TakeStartLine(SocketInput input)
678679
{
679680
return false;
680681
}
681-
var httpVersion = begin.GetAsciiString(scan);
682+
var httpVersion = begin.GetAsciiString(scan, _stringPool);
682683

683684
scan.Take();
684685
if (scan.Take() != '\n')
@@ -699,7 +700,7 @@ protected bool TakeStartLine(SocketInput input)
699700
else
700701
{
701702
// URI wasn't encoded, parse as ASCII
702-
requestUrlPath = pathBegin.GetAsciiString(pathEnd);
703+
requestUrlPath = pathBegin.GetAsciiString(pathEnd, _stringPool);
703704
}
704705

705706
consumed = scan;
@@ -752,7 +753,7 @@ private bool RequestUrlStartsWithPathBase(string requestUrl, out bool caseMatche
752753
return true;
753754
}
754755

755-
public static bool TakeMessageHeaders(SocketInput input, FrameRequestHeaders requestHeaders)
756+
public static bool TakeMessageHeaders(SocketInput input, FrameRequestHeaders requestHeaders, StringPool stringPool)
756757
{
757758
var scan = input.ConsumingStart();
758759
var consumed = scan;
@@ -843,7 +844,7 @@ public static bool TakeMessageHeaders(SocketInput input, FrameRequestHeaders req
843844
}
844845

845846
var name = beginName.GetArraySegment(endName);
846-
var value = beginValue.GetAsciiString(endValue);
847+
var value = beginValue.GetAsciiString(endValue, stringPool);
847848
if (wrapping)
848849
{
849850
value = value.Replace("\r\n", " ");

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ public override async Task RequestProcessingAsync()
4343
var terminated = false;
4444
while (!terminated && !_requestProcessingStopping)
4545
{
46+
_stringPool.MarkStart();
47+
4648
while (!terminated && !_requestProcessingStopping && !TakeStartLine(SocketInput))
4749
{
4850
terminated = SocketInput.RemoteIntakeFin;
@@ -52,7 +54,7 @@ public override async Task RequestProcessingAsync()
5254
}
5355
}
5456

55-
while (!terminated && !_requestProcessingStopping && !TakeMessageHeaders(SocketInput, _requestHeaders))
57+
while (!terminated && !_requestProcessingStopping && !TakeMessageHeaders(SocketInput, _requestHeaders, _stringPool))
5658
{
5759
terminated = SocketInput.RemoteIntakeFin;
5860
if (!terminated)

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

Lines changed: 91 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,80 +11,128 @@ public static class MemoryPoolIterator2Extensions
1111
private const int _maxStackAllocBytes = 16384;
1212

1313
private static Encoding _utf8 = Encoding.UTF8;
14+
private static uint _startHash;
1415

15-
private static unsafe string GetAsciiStringStack(byte[] input, int inputOffset, int length)
16+
static MemoryPoolIterator2Extensions()
17+
{
18+
using (var rnd = System.Security.Cryptography.RandomNumberGenerator.Create())
19+
{
20+
var randomBytes = new byte[8];
21+
rnd.GetBytes(randomBytes);
22+
_startHash =
23+
(randomBytes[0]) |
24+
(((uint)randomBytes[1]) << 8) |
25+
(((uint)randomBytes[2]) << 16) |
26+
(((uint)randomBytes[3]) << 24);
27+
}
28+
}
29+
30+
private static unsafe string GetAsciiStringStack(byte* input, int length, StringPool stringPool)
1631
{
1732
// avoid declaring other local vars, or doing work with stackalloc
1833
// to prevent the .locals init cil flag , see: https://github.com/dotnet/coreclr/issues/1279
1934
char* output = stackalloc char[length];
2035

21-
return GetAsciiStringImplementation(output, input, inputOffset, length);
36+
return GetAsciiStringImplementation(output, input, length, stringPool);
2237
}
23-
private static unsafe string GetAsciiStringImplementation(char* output, byte[] input, int inputOffset, int length)
38+
private static unsafe string GetAsciiStringImplementation(char* outputStart, byte* input, int length, StringPool stringPool)
2439
{
25-
for (var i = 0; i < length; i++)
40+
var hash = _startHash;
41+
42+
var output = outputStart;
43+
var i = 0;
44+
var lengthMinusSpan = length - 3;
45+
for (; i < lengthMinusSpan; i += 4)
46+
{
47+
// span hashing with fix https://github.com/dotnet/corefxlab/pull/455
48+
hash = hash * 31 + *((uint*)input);
49+
50+
*(output) = (char)*(input);
51+
*(output + 1) = (char)*(input + 1);
52+
*(output + 2) = (char)*(input + 2);
53+
*(output + 3) = (char)*(input + 3);
54+
output += 4;
55+
input += 4;
56+
}
57+
for (; i < length; i++)
2658
{
27-
output[i] = (char)input[inputOffset + i];
59+
hash = hash * 31 + *((uint*)input);
60+
*(output++) = (char)*(input++);
2861
}
2962

30-
return new string(output, 0, length);
63+
return stringPool.GetString(outputStart, hash, length);
3164
}
3265

33-
private static unsafe string GetAsciiStringStack(MemoryPoolBlock2 start, MemoryPoolIterator2 end, int inputOffset, int length)
66+
private static unsafe string GetAsciiStringStack(MemoryPoolBlock2 start, MemoryPoolIterator2 end, int inputOffset, int length, StringPool stringPool)
3467
{
3568
// avoid declaring other local vars, or doing work with stackalloc
3669
// to prevent the .locals init cil flag , see: https://github.com/dotnet/coreclr/issues/1279
3770
char* output = stackalloc char[length];
3871

39-
return GetAsciiStringImplementation(output, start, end, inputOffset, length);
72+
return GetAsciiStringImplementation(output, start, end, inputOffset, length, stringPool);
4073
}
4174

42-
private unsafe static string GetAsciiStringHeap(MemoryPoolBlock2 start, MemoryPoolIterator2 end, int inputOffset, int length)
75+
private unsafe static string GetAsciiStringHeap(MemoryPoolBlock2 start, MemoryPoolIterator2 end, int inputOffset, int length, StringPool stringPool)
4376
{
4477
var buffer = new char[length];
4578

4679
fixed (char* output = buffer)
4780
{
48-
return GetAsciiStringImplementation(output, start, end, inputOffset, length);
81+
return GetAsciiStringImplementation(output, start, end, inputOffset, length, stringPool);
4982
}
5083
}
5184

52-
private static unsafe string GetAsciiStringImplementation(char* output, MemoryPoolBlock2 start, MemoryPoolIterator2 end, int inputOffset, int length)
85+
private static unsafe string GetAsciiStringImplementation(char* outputStart, MemoryPoolBlock2 start, MemoryPoolIterator2 end, int inputOffset, int length, StringPool stringPool)
5386
{
54-
var outputOffset = 0;
87+
var hash = _startHash;
88+
89+
var output = outputStart;
90+
5591
var block = start;
5692
var remaining = length;
5793

5894
var endBlock = end.Block;
5995
var endIndex = end.Index;
6096

61-
while (true)
97+
while (remaining > 0)
6298
{
6399
int following = (block != endBlock ? block.End : endIndex) - inputOffset;
64100

65101
if (following > 0)
66102
{
67-
var input = block.Array;
68-
for (var i = 0; i < following; i++)
103+
fixed (byte* blockStart = block.Array)
69104
{
70-
output[i + outputOffset] = (char)input[i + inputOffset];
71-
}
105+
var input = blockStart + inputOffset;
106+
var i = 0;
107+
var followingMinusSpan = following - 3;
108+
for (; i < followingMinusSpan; i += 4)
109+
{
110+
// span hashing with fix https://github.com/dotnet/corefxlab/pull/455
111+
hash = hash * 31 + *((uint*)input);
72112

113+
*(output) = (char)*(input);
114+
*(output + 1) = (char)*(input + 1);
115+
*(output + 2) = (char)*(input + 2);
116+
*(output + 3) = (char)*(input + 3);
117+
output += 4;
118+
input += 4;
119+
}
120+
for (; i < following; i++)
121+
{
122+
hash = hash * 31 + *((uint*)input);
123+
*(output++) = (char)*(input++);
124+
}
125+
}
73126
remaining -= following;
74-
outputOffset += following;
75-
}
76-
77-
if (remaining == 0)
78-
{
79-
return new string(output, 0, length);
80127
}
81128

82129
block = block.Next;
83-
inputOffset = block.Start;
130+
inputOffset = block?.Start??0;
84131
}
132+
return stringPool.GetString(outputStart, hash, length);
85133
}
86134

87-
public static string GetAsciiString(this MemoryPoolIterator2 start, MemoryPoolIterator2 end)
135+
public unsafe static string GetAsciiString(this MemoryPoolIterator2 start, MemoryPoolIterator2 end, StringPool stringPool)
88136
{
89137
if (start.IsDefault || end.IsDefault)
90138
{
@@ -93,20 +141,28 @@ public static string GetAsciiString(this MemoryPoolIterator2 start, MemoryPoolIt
93141

94142
var length = start.GetLength(end);
95143

144+
if (length <= 0)
145+
{
146+
return default(string);
147+
}
148+
96149
// Bytes out of the range of ascii are treated as "opaque data"
97150
// and kept in string as a char value that casts to same input byte value
98151
// https://tools.ietf.org/html/rfc7230#section-3.2.4
99152
if (end.Block == start.Block)
100153
{
101-
return GetAsciiStringStack(start.Block.Array, start.Index, length);
154+
fixed (byte* input = start.Block.Array)
155+
{
156+
return GetAsciiStringStack(input + start.Index, length, stringPool);
157+
}
102158
}
103159

104160
if (length > _maxStackAllocBytes)
105161
{
106-
return GetAsciiStringHeap(start.Block, end, start.Index, length);
162+
return GetAsciiStringHeap(start.Block, end, start.Index, length, stringPool);
107163
}
108164

109-
return GetAsciiStringStack(start.Block, end, start.Index, length);
165+
return GetAsciiStringStack(start.Block, end, start.Index, length, stringPool);
110166
}
111167

112168
public static string GetUtf8String(this MemoryPoolIterator2 start, MemoryPoolIterator2 end)
@@ -120,9 +176,14 @@ public static string GetUtf8String(this MemoryPoolIterator2 start, MemoryPoolIte
120176
return _utf8.GetString(start.Block.Array, start.Index, end.Index - start.Index);
121177
}
122178

123-
var decoder = _utf8.GetDecoder();
124-
125179
var length = start.GetLength(end);
180+
181+
if (length <= 0)
182+
{
183+
return default(string);
184+
}
185+
186+
var decoder = _utf8.GetDecoder();
126187
var charLength = length * 2;
127188
var chars = new char[charLength];
128189
var charIndex = 0;
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
6+
namespace Microsoft.AspNet.Server.Kestrel.Infrastructure
7+
{
8+
public class StringPool
9+
{
10+
// x64 int array byte size (28 + length * 4) rounded up to 8 bytes
11+
// x86 int array byte size (12 + length * 4) rounded up to 4 bytes
12+
// Array of 25 ints is 2 consecutive cache lines on x64; second prefetched
13+
// Array of 9 ints is 1 cache line on x64
14+
private const int _maxCached = 25;
15+
private const int _maxCacheLength = 256;
16+
17+
private readonly uint[] _hashes = new uint[_maxCached];
18+
private readonly int[] _lastUse = new int[_maxCached];
19+
private readonly string[] _strings = new string[_maxCached];
20+
21+
private int _currentUse = 0;
22+
23+
public void MarkStart()
24+
{
25+
_currentUse++;
26+
}
27+
28+
public unsafe string GetString(char* data, uint hash, int length)
29+
{
30+
if (length > _maxCacheLength)
31+
{
32+
return new string(data, 0, length);
33+
}
34+
35+
int oldestEntry = int.MaxValue;
36+
int oldestIndex = 0;
37+
38+
for (var i = 0; i < _maxCached; i++)
39+
{
40+
var usage = _lastUse[i];
41+
if (oldestEntry > usage)
42+
{
43+
oldestEntry = usage;
44+
oldestIndex = i;
45+
}
46+
47+
if (hash == _hashes[i])
48+
{
49+
var cachedString = _strings[i];
50+
if (cachedString.Length != length)
51+
{
52+
#if DEBUG
53+
Console.WriteLine($"{nameof(StringPool)} Collision differing lengths {cachedString.Length} and {length}");
54+
#endif
55+
continue;
56+
}
57+
58+
fixed(char* cs = cachedString)
59+
{
60+
var cached = cs;
61+
var potential = data;
62+
63+
var c = 0;
64+
var lengthMinusSpan = length - 3;
65+
for (; c < lengthMinusSpan; c += 4)
66+
{
67+
if(
68+
*(cached) != *(potential) ||
69+
*(cached + 1) != *(potential + 1) ||
70+
*(cached + 2) != *(potential + 2) ||
71+
*(cached + 3) != *(potential + 3)
72+
)
73+
{
74+
#if DEBUG
75+
Console.WriteLine($"{nameof(StringPool)} Collision same length, differing strings");
76+
#endif
77+
continue;
78+
}
79+
cached += 4;
80+
potential += 4;
81+
}
82+
for (; c < length; c++)
83+
{
84+
if (*(cached++) != *(potential++))
85+
{
86+
#if DEBUG
87+
Console.WriteLine($"{nameof(StringPool)} Collision same length, differing strings");
88+
#endif
89+
continue;
90+
}
91+
}
92+
}
93+
94+
_lastUse[i] = _currentUse;
95+
// same string
96+
return cachedString;
97+
}
98+
}
99+
100+
var value = new string(data, 0, length);
101+
#if DEBUG
102+
if (_lastUse[oldestIndex] != 0)
103+
{
104+
Console.WriteLine($"{nameof(StringPool)} Evict: {_strings[oldestIndex]} {_lastUse[oldestIndex]} {_hashes[oldestIndex]}");
105+
Console.WriteLine($"{nameof(StringPool)} New: {value} {_currentUse} {hash}");
106+
}
107+
#endif
108+
_lastUse[oldestIndex] = _currentUse;
109+
_hashes[oldestIndex] = hash;
110+
_strings[oldestIndex] = value;
111+
112+
return value;
113+
}
114+
}
115+
}

0 commit comments

Comments
 (0)