diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolIteratorExtensions.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolIteratorExtensions.cs index 95c3c5a59f..950ae4bc40 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolIteratorExtensions.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/MemoryPoolIteratorExtensions.cs @@ -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. using System; @@ -19,6 +19,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure private readonly static ulong _httpConnectMethodLong = GetAsciiStringAsLong("CONNECT "); private readonly static ulong _httpDeleteMethodLong = GetAsciiStringAsLong("DELETE \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 _httpPatchMethodLong = GetAsciiStringAsLong("PATCH \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 _httpTraceMethodLong = GetAsciiStringAsLong("TRACE \0\0"); - private readonly static ulong _http10VersionLong = GetAsciiStringAsLong("HTTP/1.0"); - private readonly static ulong _http11VersionLong = GetAsciiStringAsLong("HTTP/1.1"); + private const ulong _http10VersionLong = 3471766442030158920; // GetAsciiStringAsLong("HTTP/1.0"); const results in better codegen + 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 _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; } } + + 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) { 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)] public static bool GetKnownMethod(this Span span, out string knownMethod) { - knownMethod = null; - if (span.Length < sizeof(ulong)) + if (span.TryRead(out var possiblyGet)) { - return false; - } - - ulong value = span.Read(); - if ((value & _mask4Chars) == _httpGetMethodLong) - { - knownMethod = HttpMethods.Get; - return true; - } - foreach (var x in _knownMethods) - { - if ((value & x.Item1) == x.Item2) + if (possiblyGet == _httpGetMethodInt) { - knownMethod = x.Item3; + knownMethod = HttpMethods.Get; return true; } } + if (span.TryRead(out var value)) + { + foreach (var x in _knownMethods) + { + if ((value & x.Item1) == x.Item2) + { + knownMethod = x.Item3; + return true; + } + } + } + + knownMethod = null; return false; } @@ -244,32 +259,30 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool GetKnownVersion(this Span span, out string knownVersion) { - knownVersion = null; - - if (span.Length < sizeof(ulong)) + if (span.TryRead(out var version)) { - return false; - } - - var value = span.Read(); - if (value == _http11VersionLong) - { - knownVersion = Http11Version; - } - else if (value == _http10VersionLong) - { - knownVersion = Http10Version; - } - - if (knownVersion != null) - { - if (span[sizeof(ulong)] != (byte)'\r') + if (version == _http11VersionLong) + { + knownVersion = Http11Version; + } + else if (version == _http10VersionLong) + { + knownVersion = Http10Version; + } + else { knownVersion = null; + return false; + } + + if (span[sizeof(ulong)] == (byte)'\r') + { + return true; } } - return knownVersion != null; + knownVersion = null; + return false; } } } diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.Performance/KnownStrings.cs b/test/Microsoft.AspNetCore.Server.Kestrel.Performance/KnownStrings.cs new file mode 100644 index 0000000000..74ef1153ff --- /dev/null +++ b/test/Microsoft.AspNetCore.Server.Kestrel.Performance/KnownStrings.cs @@ -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 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 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; + } + } +} diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.Performance/Program.cs b/test/Microsoft.AspNetCore.Server.Kestrel.Performance/Program.cs index d687d6bea2..e6bea9e92c 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.Performance/Program.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.Performance/Program.cs @@ -40,6 +40,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance { BenchmarkRunner.Run(); } + if (type.HasFlag(BenchmarkType.KnownStrings)) + { + BenchmarkRunner.Run(); + } } } @@ -49,6 +53,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance RequestParsing = 1, Writing = 2, Throughput = 4, + KnownStrings = 8, // add new ones in powers of two - e.g. 2,4,8,16... All = uint.MaxValue