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

Commit 349af50

Browse files
author
Cesar Blum Silveira
committed
Pre-allocate standard method and version strings.
1 parent 83cff16 commit 349af50

File tree

4 files changed

+262
-2
lines changed

4 files changed

+262
-2
lines changed

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -701,7 +701,12 @@ protected bool TakeStartLine(SocketInput input)
701701
{
702702
return false;
703703
}
704-
var method = begin.GetAsciiString(scan);
704+
705+
string method;
706+
if (!begin.GetKnownString(scan, out method))
707+
{
708+
method = begin.GetAsciiString(scan);
709+
}
705710

706711
scan.Take();
707712
begin = scan;
@@ -734,7 +739,12 @@ protected bool TakeStartLine(SocketInput input)
734739
{
735740
return false;
736741
}
737-
var httpVersion = begin.GetAsciiString(scan);
742+
743+
string httpVersion;
744+
if (!begin.GetKnownString(scan, out httpVersion))
745+
{
746+
httpVersion = begin.GetAsciiString(scan);
747+
}
738748

739749
scan.Take();
740750
if (scan.Take() != '\n')

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,49 @@ public int Peek()
138138
}
139139
}
140140

141+
public unsafe long PeekLong()
142+
{
143+
if (_block == null)
144+
{
145+
return -1;
146+
}
147+
else if (_block.End - _index >= sizeof(long))
148+
{
149+
fixed (byte* ptr = _block.Array)
150+
{
151+
return *(long*)(ptr + _index);
152+
}
153+
}
154+
else if (_block.Next == null)
155+
{
156+
return -1;
157+
}
158+
else
159+
{
160+
var blockBytes = _block.End - _index;
161+
var nextBytes = sizeof(long) - blockBytes;
162+
163+
if (_block.Next.End - _block.Next.Start < nextBytes)
164+
{
165+
return -1;
166+
}
167+
168+
long blockLong;
169+
fixed (byte* ptr = _block.Array)
170+
{
171+
blockLong = *(long*)(ptr + _block.End - sizeof(long));
172+
}
173+
174+
long nextLong;
175+
fixed (byte* ptr = _block.Next.Array)
176+
{
177+
nextLong = *(long*)(ptr + _block.Next.Start);
178+
}
179+
180+
return (blockLong >> (sizeof(long) - blockBytes) * 8) | (nextLong << (sizeof(long) - nextBytes) * 8);
181+
}
182+
}
183+
141184
public int Seek(int char0)
142185
{
143186
if (IsDefault)

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

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Diagnostics;
56
using System.Text;
67

78
namespace Microsoft.AspNet.Server.Kestrel.Infrastructure
@@ -12,6 +13,62 @@ public static class MemoryPoolIterator2Extensions
1213

1314
private static Encoding _utf8 = Encoding.UTF8;
1415

16+
public const string HttpConnectMethod = "CONNECT";
17+
public const string HttpDeleteMethod = "DELETE";
18+
public const string HttpGetMethod = "GET";
19+
public const string HttpHeadMethod = "HEAD";
20+
public const string HttpPatchMethod = "PATCH";
21+
public const string HttpPostMethod = "POST";
22+
public const string HttpPutMethod = "PUT";
23+
public const string HttpOptionsMethod = "OPTIONS";
24+
public const string HttpTraceMethod = "TRACE";
25+
26+
public const string Http10Version = "HTTP/1.0";
27+
public const string Http11Version = "HTTP/1.1";
28+
29+
private static long _httpConnectMethodLong = GetAsciiStringAsLong("CONNECT\0");
30+
private static long _httpDeleteMethodLong = GetAsciiStringAsLong("DELETE\0\0");
31+
private static long _httpGetMethodLong = GetAsciiStringAsLong("GET\0\0\0\0\0");
32+
private static long _httpHeadMethodLong = GetAsciiStringAsLong("HEAD\0\0\0\0");
33+
private static long _httpPatchMethodLong = GetAsciiStringAsLong("PATCH\0\0\0");
34+
private static long _httpPostMethodLong = GetAsciiStringAsLong("POST\0\0\0\0");
35+
private static long _httpPutMethodLong = GetAsciiStringAsLong("PUT\0\0\0\0\0");
36+
private static long _httpOptionsMethodLong = GetAsciiStringAsLong("OPTIONS\0");
37+
private static long _httpTraceMethodLong = GetAsciiStringAsLong("TRACE\0\0\0");
38+
39+
private static long _http10VersionLong = GetAsciiStringAsLong("HTTP/1.0");
40+
private static long _http11VersionLong = GetAsciiStringAsLong("HTTP/1.1");
41+
42+
private const int PerfectHashDivisor = 37;
43+
private static Tuple<long, string>[] _knownStrings = new Tuple<long, string>[PerfectHashDivisor];
44+
45+
static MemoryPoolIterator2Extensions()
46+
{
47+
_knownStrings[_httpConnectMethodLong % PerfectHashDivisor] = Tuple.Create(_httpConnectMethodLong, HttpConnectMethod);
48+
_knownStrings[_httpDeleteMethodLong % PerfectHashDivisor] = Tuple.Create(_httpDeleteMethodLong, HttpDeleteMethod);
49+
_knownStrings[_httpGetMethodLong % PerfectHashDivisor] = Tuple.Create(_httpGetMethodLong, HttpGetMethod);
50+
_knownStrings[_httpHeadMethodLong % PerfectHashDivisor] = Tuple.Create(_httpHeadMethodLong, HttpHeadMethod);
51+
_knownStrings[_httpPatchMethodLong % PerfectHashDivisor] = Tuple.Create(_httpPatchMethodLong, HttpPatchMethod);
52+
_knownStrings[_httpPostMethodLong % PerfectHashDivisor] = Tuple.Create(_httpPostMethodLong, HttpPostMethod);
53+
_knownStrings[_httpPutMethodLong % PerfectHashDivisor] = Tuple.Create(_httpPutMethodLong, HttpPutMethod);
54+
_knownStrings[_httpOptionsMethodLong % PerfectHashDivisor] = Tuple.Create(_httpOptionsMethodLong, HttpOptionsMethod);
55+
_knownStrings[_httpTraceMethodLong % PerfectHashDivisor] = Tuple.Create(_httpTraceMethodLong, HttpTraceMethod);
56+
_knownStrings[_http10VersionLong % PerfectHashDivisor] = Tuple.Create(_http10VersionLong, Http10Version);
57+
_knownStrings[_http11VersionLong % PerfectHashDivisor] = Tuple.Create(_http11VersionLong, Http11Version);
58+
}
59+
60+
private unsafe static long GetAsciiStringAsLong(string str)
61+
{
62+
Debug.Assert(str.Length == 8, "String must be exactly 8 (ASCII) characters long.");
63+
64+
var bytes = Encoding.ASCII.GetBytes(str);
65+
66+
fixed (byte* ptr = bytes)
67+
{
68+
return *(long*)ptr;
69+
}
70+
}
71+
1572
private static unsafe string GetAsciiStringStack(byte[] input, int inputOffset, int length)
1673
{
1774
// avoid declaring other local vars, or doing work with stackalloc
@@ -20,6 +77,7 @@ private static unsafe string GetAsciiStringStack(byte[] input, int inputOffset,
2077

2178
return GetAsciiStringImplementation(output, input, inputOffset, length);
2279
}
80+
2381
private static unsafe string GetAsciiStringImplementation(char* output, byte[] input, int inputOffset, int length)
2482
{
2583
for (var i = 0; i < length; i++)
@@ -203,5 +261,55 @@ public static ArraySegment<byte> GetArraySegment(this MemoryPoolIterator2 start,
203261
start.CopyTo(array, 0, length, out length);
204262
return new ArraySegment<byte>(array, 0, length);
205263
}
264+
265+
/// <summary>
266+
/// Checks that up to 8 bytes between <paramref name="begin"/> and <paramref name="end"/> correspond to a known HTTP string.
267+
/// </summary>
268+
/// <remarks>
269+
/// A "known HTTP string" can be an HTTP method name defined in the HTTP/1.1 RFC or an HTTP version (HTTP/1.0 or HTTP/1.1).
270+
/// Since all of those fit in at most 8 bytes, they can be optimally looked up by reading those bytes as a long. Once
271+
/// in that format, uninteresting bits are cleared and the remaining long modulo 37 is looked up in a table.
272+
/// The number 37 was chosen because that number allows for a perfect hash of the set of
273+
/// "known strings" (CONNECT, DELETE, GET, HEAD, PATCH, POST, PUT, OPTIONS, TRACE, HTTP/1.0 and HTTP/1.1, where strings
274+
/// with less than 8 characters have 0s appended to their ends to fill for the missing bytes).
275+
/// </remarks>
276+
/// <param name="begin">The iterator from which to start the known string lookup.</param>
277+
/// <param name="end">The iterator pointing to the end of the input string.</param>
278+
/// <param name="knownString">A reference to a pre-allocated known string, if the input matches any.</param>
279+
/// <returns><c>true</c> if the input matches a known string, <c>false</c> otherwise.</returns>
280+
public static bool GetKnownString(this MemoryPoolIterator2 begin, MemoryPoolIterator2 end, out string knownString)
281+
{
282+
knownString = null;
283+
284+
// This optimization only works on little endian environments (for now).
285+
if (!BitConverter.IsLittleEndian)
286+
{
287+
return false;
288+
}
289+
290+
var inputLength = begin.GetLength(end);
291+
292+
if (inputLength > sizeof(long))
293+
{
294+
return false;
295+
}
296+
297+
var inputLong = begin.PeekLong();
298+
299+
if (inputLong == -1)
300+
{
301+
return false;
302+
}
303+
304+
inputLong &= (long)(unchecked((ulong)~0) >> ((sizeof(long) - inputLength) * 8));
305+
306+
var value = _knownStrings[inputLong % PerfectHashDivisor];
307+
if (value != null && value.Item1 == inputLong)
308+
{
309+
knownString = value.Item2;
310+
}
311+
312+
return knownString != null;
313+
}
206314
}
207315
}

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

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,5 +128,104 @@ public void Put()
128128
// Can't put anything by the end
129129
Assert.False(head.Put(0xFF));
130130
}
131+
132+
[Fact]
133+
public void PeekLong()
134+
{
135+
// Arrange
136+
var block = _pool.Lease();
137+
var bytes = BitConverter.GetBytes(0x0102030405060708);
138+
Buffer.BlockCopy(bytes, 0, block.Array, block.Start, bytes.Length);
139+
block.End += bytes.Length;
140+
var scan = block.GetIterator();
141+
var originalIndex = scan.Index;
142+
143+
// Act
144+
var result = scan.PeekLong();
145+
146+
// Assert
147+
Assert.Equal(0x0102030405060708, result);
148+
Assert.Equal(originalIndex, scan.Index);
149+
}
150+
151+
[Theory]
152+
[InlineData(1)]
153+
[InlineData(2)]
154+
[InlineData(3)]
155+
[InlineData(4)]
156+
[InlineData(5)]
157+
[InlineData(6)]
158+
[InlineData(7)]
159+
public void PeekLongAtBlockBoundary(int blockBytes)
160+
{
161+
// Arrange
162+
var nextBlockBytes = 8 - blockBytes;
163+
164+
var block = _pool.Lease();
165+
block.End += blockBytes;
166+
167+
var nextBlock = _pool.Lease();
168+
nextBlock.End += nextBlockBytes;
169+
170+
block.Next = nextBlock;
171+
172+
var bytes = BitConverter.GetBytes(0x0102030405060708);
173+
Buffer.BlockCopy(bytes, 0, block.Array, block.Start, blockBytes);
174+
Buffer.BlockCopy(bytes, blockBytes, nextBlock.Array, nextBlock.Start, nextBlockBytes);
175+
176+
var scan = block.GetIterator();
177+
var originalIndex = scan.Index;
178+
179+
// Act
180+
var result = scan.PeekLong();
181+
182+
// Assert
183+
Assert.Equal(0x0102030405060708, result);
184+
Assert.Equal(originalIndex, scan.Index);
185+
}
186+
187+
[Theory]
188+
[InlineData("CONNECT / HTTP/1.1", ' ', true, MemoryPoolIterator2Extensions.HttpConnectMethod)]
189+
[InlineData("DELETE / HTTP/1.1", ' ', true, MemoryPoolIterator2Extensions.HttpDeleteMethod)]
190+
[InlineData("GET / HTTP/1.1", ' ', true, MemoryPoolIterator2Extensions.HttpGetMethod)]
191+
[InlineData("HEAD / HTTP/1.1", ' ', true, MemoryPoolIterator2Extensions.HttpHeadMethod)]
192+
[InlineData("PATCH / HTTP/1.1", ' ', true, MemoryPoolIterator2Extensions.HttpPatchMethod)]
193+
[InlineData("POST / HTTP/1.1", ' ', true, MemoryPoolIterator2Extensions.HttpPostMethod)]
194+
[InlineData("PUT / HTTP/1.1", ' ', true, MemoryPoolIterator2Extensions.HttpPutMethod)]
195+
[InlineData("OPTIONS / HTTP/1.1", ' ', true, MemoryPoolIterator2Extensions.HttpOptionsMethod)]
196+
[InlineData("TRACE / HTTP/1.1", ' ', true, MemoryPoolIterator2Extensions.HttpTraceMethod)]
197+
[InlineData("HTTP/1.0\r", '\r', true, MemoryPoolIterator2Extensions.Http10Version)]
198+
[InlineData("HTTP/1.1\r", '\r', true, MemoryPoolIterator2Extensions.Http11Version)]
199+
[InlineData("GET/ HTTP/1.1", ' ', false, null)]
200+
[InlineData("get / HTTP/1.1", ' ', false, null)]
201+
[InlineData("GOT / HTTP/1.1", ' ', false, null)]
202+
[InlineData("ABC / HTTP/1.1", ' ', false, null)]
203+
[InlineData("PO / HTTP/1.1", ' ', false, null)]
204+
[InlineData("PO ST / HTTP/1.1", ' ', false, null)]
205+
[InlineData("HTTP/1.0_\r", '\r', false, null)]
206+
[InlineData("HTTP/1.1_\r", '\r', false, null)]
207+
[InlineData("HTTP/3.0\r", '\r', false, null)]
208+
[InlineData("http/1.0\r", '\r', false, null)]
209+
[InlineData("http/1.1\r", '\r', false, null)]
210+
[InlineData("short ", ' ', false, null)]
211+
public void GetsKnownString(string input, char endChar, bool expectedResult, string expectedKnownString)
212+
{
213+
// Arrange
214+
var block = _pool.Lease();
215+
var chars = input.ToCharArray().Select(c => (byte)c).ToArray();
216+
Buffer.BlockCopy(chars, 0, block.Array, block.Start, chars.Length);
217+
block.End += chars.Length;
218+
var begin = block.GetIterator();
219+
var end = begin;
220+
end.Seek(endChar);
221+
string knownString;
222+
223+
// Act
224+
var result = begin.GetKnownString(end, out knownString);
225+
226+
// Assert
227+
Assert.Equal(expectedResult, result);
228+
Assert.Equal(expectedKnownString, knownString);
229+
}
131230
}
132231
}

0 commit comments

Comments
 (0)