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:
parent
64b6563249
commit
c56de066d3
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue