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

Commit 479a546

Browse files
committed
Request Header String Pool
1 parent 706ff04 commit 479a546

File tree

12 files changed

+307
-49
lines changed

12 files changed

+307
-49
lines changed

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

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

7272
private readonly string _pathBase;
73+
protected readonly IStringCache _stringCache;
7374

7475
public Frame(ConnectionContext context)
75-
: this(context, remoteEndPoint: null, localEndPoint: null, prepareRequest: null)
76+
: this(context, remoteEndPoint: null, localEndPoint: null, prepareRequest: null, stringCache: null)
7677
{
7778
}
7879

7980
public Frame(ConnectionContext context,
8081
IPEndPoint remoteEndPoint,
8182
IPEndPoint localEndPoint,
82-
Action<IFeatureCollection> prepareRequest)
83+
Action<IFeatureCollection> prepareRequest,
84+
IStringCache stringCache)
8385
: base(context)
8486
{
8587
_remoteEndPoint = remoteEndPoint;
8688
_localEndPoint = localEndPoint;
8789
_prepareRequest = prepareRequest;
8890
_pathBase = context.ServerAddress.PathBase;
91+
_stringCache = stringCache;
8992

9093
FrameControl = this;
9194
Reset();
@@ -702,7 +705,7 @@ protected bool TakeStartLine(SocketInput input)
702705
{
703706
return false;
704707
}
705-
var method = begin.GetAsciiString(scan);
708+
var method = begin.GetAsciiString(scan, _stringCache);
706709

707710
scan.Take();
708711
begin = scan;
@@ -726,7 +729,7 @@ protected bool TakeStartLine(SocketInput input)
726729
{
727730
return false;
728731
}
729-
queryString = begin.GetAsciiString(scan);
732+
queryString = begin.GetAsciiString(scan, _stringCache);
730733
}
731734

732735
scan.Take();
@@ -735,7 +738,7 @@ protected bool TakeStartLine(SocketInput input)
735738
{
736739
return false;
737740
}
738-
var httpVersion = begin.GetAsciiString(scan);
741+
var httpVersion = begin.GetAsciiString(scan, _stringCache);
739742

740743
scan.Take();
741744
if (scan.Take() != '\n')
@@ -756,7 +759,7 @@ protected bool TakeStartLine(SocketInput input)
756759
else
757760
{
758761
// URI wasn't encoded, parse as ASCII
759-
requestUrlPath = pathBegin.GetAsciiString(pathEnd);
762+
requestUrlPath = pathBegin.GetAsciiString(pathEnd, _stringCache);
760763
}
761764

762765
consumed = scan;
@@ -809,7 +812,7 @@ private bool RequestUrlStartsWithPathBase(string requestUrl, out bool caseMatche
809812
return true;
810813
}
811814

812-
public static bool TakeMessageHeaders(SocketInput input, FrameRequestHeaders requestHeaders)
815+
public static bool TakeMessageHeaders(SocketInput input, FrameRequestHeaders requestHeaders, IStringCache stringCache)
813816
{
814817
var scan = input.ConsumingStart();
815818
var consumed = scan;
@@ -900,7 +903,7 @@ public static bool TakeMessageHeaders(SocketInput input, FrameRequestHeaders req
900903
}
901904

902905
var name = beginName.GetArraySegment(endName);
903-
var value = beginValue.GetAsciiString(endValue);
906+
var value = beginValue.GetAsciiString(endValue, stringCache);
904907
if (wrapping)
905908
{
906909
value = value.Replace("\r\n", " ");

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Threading.Tasks;
77
using Microsoft.AspNet.Hosting.Server;
88
using Microsoft.AspNet.Http.Features;
9+
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
910
using Microsoft.Extensions.Logging;
1011

1112
namespace Microsoft.AspNet.Server.Kestrel.Http
@@ -16,16 +17,17 @@ public class Frame<TContext> : Frame
1617

1718
public Frame(IHttpApplication<TContext> application,
1819
ConnectionContext context)
19-
: this(application, context, remoteEndPoint: null, localEndPoint: null, prepareRequest: null)
20+
: this(application, context, remoteEndPoint: null, localEndPoint: null, prepareRequest: null, stringCache: null)
2021
{
2122
}
2223

2324
public Frame(IHttpApplication<TContext> application,
2425
ConnectionContext context,
2526
IPEndPoint remoteEndPoint,
2627
IPEndPoint localEndPoint,
27-
Action<IFeatureCollection> prepareRequest)
28-
: base(context, remoteEndPoint, localEndPoint, prepareRequest)
28+
Action<IFeatureCollection> prepareRequest,
29+
IStringCache stringCache)
30+
: base(context, remoteEndPoint, localEndPoint, prepareRequest, stringCache)
2931
{
3032
_application = application;
3133
}
@@ -42,6 +44,8 @@ public override async Task RequestProcessingAsync()
4244
{
4345
while (!_requestProcessingStopping)
4446
{
47+
_stringCache?.MarkStart();
48+
4549
while (!_requestProcessingStopping && !TakeStartLine(SocketInput))
4650
{
4751
if (SocketInput.RemoteIntakeFin)
@@ -51,7 +55,7 @@ public override async Task RequestProcessingAsync()
5155
await SocketInput;
5256
}
5357

54-
while (!_requestProcessingStopping && !TakeMessageHeaders(SocketInput, _requestHeaders))
58+
while (!_requestProcessingStopping && !TakeMessageHeaders(SocketInput, _requestHeaders, _stringCache))
5559
{
5660
if (SocketInput.RemoteIntakeFin)
5761
{

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ public interface IKestrelServerInformation
1111

1212
bool NoDelay { get; set; }
1313

14+
bool StringCacheOnConnection { get; set; }
15+
16+
int StringCacheMaxStrings { get; set; }
17+
18+
int StringCacheMaxStringLength { get; set; }
19+
1420
IConnectionFilter ConnectionFilter { get; set; }
1521
}
1622
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace Microsoft.AspNet.Server.Kestrel.Infrastructure
2+
{
3+
public interface IStringCache
4+
{
5+
void MarkStart();
6+
unsafe string GetString(char* data, uint hash, int length);
7+
}
8+
}

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, IStringCache stringCache)
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, stringCache);
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, IStringCache stringCache)
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 + *((char*)input);
60+
*(output++) = (char)*(input++);
2861
}
2962

30-
return new string(output, 0, length);
63+
return stringCache?.GetString(outputStart, hash, length) ?? new string(outputStart, 0, 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, IStringCache stringCache)
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, stringCache);
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, IStringCache stringCache)
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, stringCache);
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, IStringCache stringCache)
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 + *((char*)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 stringCache?.GetString(outputStart, hash, length) ?? new string(outputStart, 0, length);
85133
}
86134

87-
public static string GetAsciiString(this MemoryPoolIterator2 start, MemoryPoolIterator2 end)
135+
public unsafe static string GetAsciiString(this MemoryPoolIterator2 start, MemoryPoolIterator2 end, IStringCache stringCache)
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, stringCache);
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, stringCache);
107163
}
108164

109-
return GetAsciiStringStack(start.Block, end, start.Index, length);
165+
return GetAsciiStringStack(start.Block, end, start.Index, length, stringCache);
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;

0 commit comments

Comments
 (0)