Optimized parsing GET verb and version (#1399)

* Optimized parsing GET verb
* optimized http version parsing
* Added microbenchmarks for GetKnownMethod and GetKnownVersion
This commit is contained in:
Krzysztof Cwalina 2017-02-28 17:02:52 -08:00 committed by Stephen Halter
parent 64b6563249
commit c56de066d3
3 changed files with 135 additions and 38 deletions

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
@ -19,6 +19,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
private readonly static ulong _httpConnectMethodLong = GetAsciiStringAsLong("CONNECT "); private readonly static ulong _httpConnectMethodLong = GetAsciiStringAsLong("CONNECT ");
private readonly static ulong _httpDeleteMethodLong = GetAsciiStringAsLong("DELETE \0"); private readonly static ulong _httpDeleteMethodLong = GetAsciiStringAsLong("DELETE \0");
private readonly static ulong _httpGetMethodLong = GetAsciiStringAsLong("GET \0\0\0\0"); private readonly static ulong _httpGetMethodLong = GetAsciiStringAsLong("GET \0\0\0\0");
private const uint _httpGetMethodInt = 542393671; // retun of GetAsciiStringAsInt("GET "); const results in better codegen
private readonly static ulong _httpHeadMethodLong = GetAsciiStringAsLong("HEAD \0\0\0"); private readonly static ulong _httpHeadMethodLong = GetAsciiStringAsLong("HEAD \0\0\0");
private readonly static ulong _httpPatchMethodLong = GetAsciiStringAsLong("PATCH \0\0"); private readonly static ulong _httpPatchMethodLong = GetAsciiStringAsLong("PATCH \0\0");
private readonly static ulong _httpPostMethodLong = GetAsciiStringAsLong("POST \0\0\0"); private readonly static ulong _httpPostMethodLong = GetAsciiStringAsLong("POST \0\0\0");
@ -26,8 +27,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
private readonly static ulong _httpOptionsMethodLong = GetAsciiStringAsLong("OPTIONS "); private readonly static ulong _httpOptionsMethodLong = GetAsciiStringAsLong("OPTIONS ");
private readonly static ulong _httpTraceMethodLong = GetAsciiStringAsLong("TRACE \0\0"); private readonly static ulong _httpTraceMethodLong = GetAsciiStringAsLong("TRACE \0\0");
private readonly static ulong _http10VersionLong = GetAsciiStringAsLong("HTTP/1.0"); private const ulong _http10VersionLong = 3471766442030158920; // GetAsciiStringAsLong("HTTP/1.0"); const results in better codegen
private readonly static ulong _http11VersionLong = GetAsciiStringAsLong("HTTP/1.1"); private const ulong _http11VersionLong = 3543824036068086856; // GetAsciiStringAsLong("HTTP/1.1"); const results in better codegen
private readonly static ulong _mask8Chars = GetMaskAsLong(new byte[] { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }); private readonly static ulong _mask8Chars = GetMaskAsLong(new byte[] { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff });
private readonly static ulong _mask7Chars = GetMaskAsLong(new byte[] { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 }); private readonly static ulong _mask7Chars = GetMaskAsLong(new byte[] { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 });
@ -60,6 +61,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
return *(ulong*)ptr; return *(ulong*)ptr;
} }
} }
private unsafe static uint GetAsciiStringAsInt(string str)
{
Debug.Assert(str.Length == 4, "String must be exactly 4 (ASCII) characters long.");
var bytes = Encoding.ASCII.GetBytes(str);
fixed (byte* ptr = &bytes[0])
{
return *(uint*)ptr;
}
}
private unsafe static ulong GetMaskAsLong(byte[] bytes) private unsafe static ulong GetMaskAsLong(byte[] bytes)
{ {
Debug.Assert(bytes.Length == 8, "Mask must be exactly 8 bytes long."); Debug.Assert(bytes.Length == 8, "Mask must be exactly 8 bytes long.");
@ -173,27 +187,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool GetKnownMethod(this Span<byte> span, out string knownMethod) public static bool GetKnownMethod(this Span<byte> span, out string knownMethod)
{ {
knownMethod = null; if (span.TryRead<uint>(out var possiblyGet))
if (span.Length < sizeof(ulong))
{ {
return false; if (possiblyGet == _httpGetMethodInt)
}
ulong value = span.Read<ulong>();
if ((value & _mask4Chars) == _httpGetMethodLong)
{
knownMethod = HttpMethods.Get;
return true;
}
foreach (var x in _knownMethods)
{
if ((value & x.Item1) == x.Item2)
{ {
knownMethod = x.Item3; knownMethod = HttpMethods.Get;
return true; return true;
} }
} }
if (span.TryRead<ulong>(out var value))
{
foreach (var x in _knownMethods)
{
if ((value & x.Item1) == x.Item2)
{
knownMethod = x.Item3;
return true;
}
}
}
knownMethod = null;
return false; return false;
} }
@ -244,32 +259,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool GetKnownVersion(this Span<byte> span, out string knownVersion) public static bool GetKnownVersion(this Span<byte> span, out string knownVersion)
{ {
knownVersion = null; if (span.TryRead<ulong>(out var version))
if (span.Length < sizeof(ulong))
{ {
return false; if (version == _http11VersionLong)
} {
knownVersion = Http11Version;
var value = span.Read<ulong>(); }
if (value == _http11VersionLong) else if (version == _http10VersionLong)
{ {
knownVersion = Http11Version; knownVersion = Http10Version;
} }
else if (value == _http10VersionLong) else
{
knownVersion = Http10Version;
}
if (knownVersion != null)
{
if (span[sizeof(ulong)] != (byte)'\r')
{ {
knownVersion = null; knownVersion = null;
return false;
}
if (span[sizeof(ulong)] == (byte)'\r')
{
return true;
} }
} }
return knownVersion != null; knownVersion = null;
return false;
} }
} }
} }

View File

@ -0,0 +1,79 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Text;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
public class KnownStrings
{
static byte[] _method = Encoding.UTF8.GetBytes("GET ");
static byte[] _version = Encoding.UTF8.GetBytes("HTTP/1.1\r\n");
const int loops = 1000;
[Benchmark(OperationsPerInvoke = loops * 10)]
public int GetKnownMethod_GET()
{
int len = 0;
string method;
Span<byte> data = _method;
for (int i = 0; i < loops; i++) {
data.GetKnownMethod(out method);
len += method.Length;
data.GetKnownMethod(out method);
len += method.Length;
data.GetKnownMethod(out method);
len += method.Length;
data.GetKnownMethod(out method);
len += method.Length;
data.GetKnownMethod(out method);
len += method.Length;
data.GetKnownMethod(out method);
len += method.Length;
data.GetKnownMethod(out method);
len += method.Length;
data.GetKnownMethod(out method);
len += method.Length;
data.GetKnownMethod(out method);
len += method.Length;
data.GetKnownMethod(out method);
len += method.Length;
}
return len;
}
[Benchmark(OperationsPerInvoke = loops * 10)]
public int GetKnownVersion_HTTP1_1()
{
int len = 0;
string version;
Span<byte> data = _version;
for (int i = 0; i < loops; i++) {
data.GetKnownVersion(out version);
len += version.Length;
data.GetKnownVersion(out version);
len += version.Length;
data.GetKnownVersion(out version);
len += version.Length;
data.GetKnownVersion(out version);
len += version.Length;
data.GetKnownVersion(out version);
len += version.Length;
data.GetKnownVersion(out version);
len += version.Length;
data.GetKnownVersion(out version);
len += version.Length;
data.GetKnownVersion(out version);
len += version.Length;
data.GetKnownVersion(out version);
len += version.Length;
data.GetKnownVersion(out version);
len += version.Length;
}
return len;
}
}
}

View File

@ -40,6 +40,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{ {
BenchmarkRunner.Run<PipeThroughput>(); BenchmarkRunner.Run<PipeThroughput>();
} }
if (type.HasFlag(BenchmarkType.KnownStrings))
{
BenchmarkRunner.Run<KnownStrings>();
}
} }
} }
@ -49,6 +53,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
RequestParsing = 1, RequestParsing = 1,
Writing = 2, Writing = 2,
Throughput = 4, Throughput = 4,
KnownStrings = 8,
// add new ones in powers of two - e.g. 2,4,8,16... // add new ones in powers of two - e.g. 2,4,8,16...
All = uint.MaxValue All = uint.MaxValue