Improve HTTP/2 headers enumerator performance (#24726)

This commit is contained in:
James Newton-King 2020-08-13 15:28:18 +12:00 committed by GitHub
parent da6aa6fc6a
commit ca1505ba3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 194 additions and 88 deletions

View File

@ -11,11 +11,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
{ {
internal sealed class Http2HeadersEnumerator : IEnumerator<KeyValuePair<string, string>> internal sealed class Http2HeadersEnumerator : IEnumerator<KeyValuePair<string, string>>
{ {
private bool _isTrailers; private enum HeadersType : byte
{
Headers,
Trailers,
Untyped
}
private HeadersType _headersType;
private HttpResponseHeaders.Enumerator _headersEnumerator; private HttpResponseHeaders.Enumerator _headersEnumerator;
private HttpResponseTrailers.Enumerator _trailersEnumerator; private HttpResponseTrailers.Enumerator _trailersEnumerator;
private IEnumerator<KeyValuePair<string, StringValues>> _genericEnumerator; private IEnumerator<KeyValuePair<string, StringValues>> _genericEnumerator;
private StringValues.Enumerator _stringValuesEnumerator; private StringValues.Enumerator _stringValuesEnumerator;
private bool _hasMultipleValues;
private KnownHeaderType _knownHeaderType; private KnownHeaderType _knownHeaderType;
public int HPackStaticTableId => GetResponseHeaderStaticTableId(_knownHeaderType); public int HPackStaticTableId => GetResponseHeaderStaticTableId(_knownHeaderType);
@ -29,136 +36,89 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
public void Initialize(HttpResponseHeaders headers) public void Initialize(HttpResponseHeaders headers)
{ {
_headersEnumerator = headers.GetEnumerator(); _headersEnumerator = headers.GetEnumerator();
_trailersEnumerator = default; _headersType = HeadersType.Headers;
_genericEnumerator = null; _hasMultipleValues = false;
_isTrailers = false;
_stringValuesEnumerator = default;
Current = default;
_knownHeaderType = default;
} }
public void Initialize(HttpResponseTrailers headers) public void Initialize(HttpResponseTrailers headers)
{ {
_headersEnumerator = default;
_trailersEnumerator = headers.GetEnumerator(); _trailersEnumerator = headers.GetEnumerator();
_genericEnumerator = null; _headersType = HeadersType.Trailers;
_isTrailers = true; _hasMultipleValues = false;
_stringValuesEnumerator = default;
Current = default;
_knownHeaderType = default;
} }
public void Initialize(IDictionary<string, StringValues> headers) public void Initialize(IDictionary<string, StringValues> headers)
{ {
_headersEnumerator = default;
_trailersEnumerator = default;
_genericEnumerator = headers.GetEnumerator(); _genericEnumerator = headers.GetEnumerator();
_isTrailers = false; _headersType = HeadersType.Untyped;
_hasMultipleValues = false;
_stringValuesEnumerator = default;
Current = default;
_knownHeaderType = default;
} }
public bool MoveNext() public bool MoveNext()
{ {
if (MoveNextOnStringEnumerator()) if (_hasMultipleValues && MoveNextOnStringEnumerator(Current.Key))
{ {
return true; return true;
} }
if (!TryGetNextStringEnumerator(out _stringValuesEnumerator)) if (_headersType == HeadersType.Headers)
{ {
return false; return _headersEnumerator.MoveNext()
? SetCurrent(_headersEnumerator.Current.Key, _headersEnumerator.Current.Value, _headersEnumerator.CurrentKnownType)
: false;
} }
else if (_headersType == HeadersType.Trailers)
return MoveNextOnStringEnumerator();
}
private string GetCurrentKey()
{
if (_genericEnumerator != null)
{ {
return _genericEnumerator.Current.Key; return _trailersEnumerator.MoveNext()
} ? SetCurrent(_trailersEnumerator.Current.Key, _trailersEnumerator.Current.Value, _trailersEnumerator.CurrentKnownType)
else if (_isTrailers) : false;
{
return _trailersEnumerator.Current.Key;
} }
else else
{ {
return _headersEnumerator.Current.Key; return _genericEnumerator.MoveNext()
? SetCurrent(_genericEnumerator.Current.Key, _genericEnumerator.Current.Value, default)
: false;
} }
} }
private bool MoveNextOnStringEnumerator() private bool MoveNextOnStringEnumerator(string key)
{ {
var result = _stringValuesEnumerator.MoveNext(); var result = _stringValuesEnumerator.MoveNext();
Current = result ? new KeyValuePair<string, string>(GetCurrentKey(), _stringValuesEnumerator.Current) : default; Current = result ? new KeyValuePair<string, string>(key, _stringValuesEnumerator.Current) : default;
return result; return result;
} }
private bool TryGetNextStringEnumerator(out StringValues.Enumerator enumerator) private bool SetCurrent(string name, StringValues value, KnownHeaderType knownHeaderType)
{ {
if (_genericEnumerator != null) if (value.Count == 1)
{ {
if (!_genericEnumerator.MoveNext()) Current = new KeyValuePair<string, string>(name, value.ToString());
{ _knownHeaderType = knownHeaderType;
enumerator = default; _hasMultipleValues = false;
return false; return true;
}
else
{
enumerator = _genericEnumerator.Current.Value.GetEnumerator();
_knownHeaderType = default;
return true;
}
}
else if (_isTrailers)
{
if (!_trailersEnumerator.MoveNext())
{
enumerator = default;
return false;
}
else
{
enumerator = _trailersEnumerator.Current.Value.GetEnumerator();
_knownHeaderType = _trailersEnumerator.CurrentKnownType;
return true;
}
} }
else else
{ {
if (!_headersEnumerator.MoveNext()) _stringValuesEnumerator = value.GetEnumerator();
{ _hasMultipleValues = true;
enumerator = default;
return false; return MoveNextOnStringEnumerator(name);
}
else
{
enumerator = _headersEnumerator.Current.Value.GetEnumerator();
_knownHeaderType = _headersEnumerator.CurrentKnownType;
return true;
}
} }
} }
public void Reset() public void Reset()
{ {
if (_genericEnumerator != null) if (_headersType == HeadersType.Headers)
{ {
_genericEnumerator.Reset(); _headersEnumerator.Reset();
} }
else if (_isTrailers) else if (_headersType == HeadersType.Trailers)
{ {
_trailersEnumerator.Reset(); _trailersEnumerator.Reset();
} }
else else
{ {
_headersEnumerator.Reset(); _genericEnumerator.Reset();
} }
_stringValuesEnumerator = default; _stringValuesEnumerator = default;
_knownHeaderType = default; _knownHeaderType = default;

View File

@ -50,18 +50,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
[Fact] [Fact]
public void CanIterateOverResponseTrailers() public void CanIterateOverResponseTrailers()
{ {
var responseHeaders = new HttpResponseTrailers var responseTrailers = new HttpResponseTrailers
{ {
ContentLength = 9, ContentLength = 9,
HeaderETag = "ETag!" HeaderETag = "ETag!"
}; };
responseHeaders.Append("Name1", "Value1"); responseTrailers.Append("Name1", "Value1");
responseHeaders.Append("Name2", "Value2-1"); responseTrailers.Append("Name2", "Value2-1");
responseHeaders.Append("Name2", "Value2-2"); responseTrailers.Append("Name2", "Value2-2");
responseHeaders.Append("Name3", "Value3"); responseTrailers.Append("Name3", "Value3");
var e = new Http2HeadersEnumerator(); var e = new Http2HeadersEnumerator();
e.Initialize(responseHeaders); e.Initialize(responseTrailers);
var headers = GetNormalizedHeaders(e); var headers = GetNormalizedHeaders(e);
@ -75,6 +75,58 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
}, headers); }, headers);
} }
[Fact]
public void Initialize_ChangeHeadersSource_EnumeratorUsesNewSource()
{
var responseHeaders = new HttpResponseHeaders();
responseHeaders.Append("Name1", "Value1");
responseHeaders.Append("Name2", "Value2-1");
responseHeaders.Append("Name2", "Value2-2");
var e = new Http2HeadersEnumerator();
e.Initialize(responseHeaders);
Assert.True(e.MoveNext());
Assert.Equal("Name1", e.Current.Key);
Assert.Equal("Value1", e.Current.Value);
Assert.True(e.MoveNext());
Assert.Equal("Name2", e.Current.Key);
Assert.Equal("Value2-1", e.Current.Value);
Assert.True(e.MoveNext());
Assert.Equal("Name2", e.Current.Key);
Assert.Equal("Value2-2", e.Current.Value);
var responseTrailers = new HttpResponseTrailers
{
HeaderGrpcStatus = "1"
};
responseTrailers.Append("Name1", "Value1");
responseTrailers.Append("Name2", "Value2-1");
responseTrailers.Append("Name2", "Value2-2");
e.Initialize(responseTrailers);
Assert.True(e.MoveNext());
Assert.Equal("Grpc-Status", e.Current.Key);
Assert.Equal("1", e.Current.Value);
Assert.True(e.MoveNext());
Assert.Equal("Name1", e.Current.Key);
Assert.Equal("Value1", e.Current.Value);
Assert.True(e.MoveNext());
Assert.Equal("Name2", e.Current.Key);
Assert.Equal("Value2-1", e.Current.Value);
Assert.True(e.MoveNext());
Assert.Equal("Name2", e.Current.Key);
Assert.Equal("Value2-2", e.Current.Value);
Assert.False(e.MoveNext());
}
private KeyValuePair<string, string>[] GetNormalizedHeaders(Http2HeadersEnumerator enumerator) private KeyValuePair<string, string>[] GetNormalizedHeaders(Http2HeadersEnumerator enumerator)
{ {
var headers = new List<KeyValuePair<string, string>>(); var headers = new List<KeyValuePair<string, string>>();

View File

@ -0,0 +1,94 @@
// 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 BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
public class Http2HeadersEnumeratorBenchmark
{
private Http2HeadersEnumerator _enumerator;
private HttpResponseHeaders _knownSingleValueResponseHeaders;
private HttpResponseHeaders _knownMultipleValueResponseHeaders;
private HttpResponseHeaders _unknownSingleValueResponseHeaders;
private HttpResponseHeaders _unknownMultipleValueResponseHeaders;
[GlobalSetup]
public void GlobalSetup()
{
_knownSingleValueResponseHeaders = new HttpResponseHeaders
{
HeaderServer = "Value",
HeaderDate = "Value",
HeaderContentType = "Value",
HeaderSetCookie = "Value"
};
_knownMultipleValueResponseHeaders = new HttpResponseHeaders
{
HeaderServer = new StringValues(new[] { "One", "Two" }),
HeaderDate = new StringValues(new[] { "One", "Two" }),
HeaderContentType = new StringValues(new[] { "One", "Two" }),
HeaderSetCookie = new StringValues(new[] { "One", "Two" })
};
_unknownSingleValueResponseHeaders = new HttpResponseHeaders();
_unknownSingleValueResponseHeaders.Append("One", "Value");
_unknownSingleValueResponseHeaders.Append("Two", "Value");
_unknownSingleValueResponseHeaders.Append("Three", "Value");
_unknownSingleValueResponseHeaders.Append("Four", "Value");
_unknownMultipleValueResponseHeaders = new HttpResponseHeaders();
_unknownMultipleValueResponseHeaders.Append("One", new StringValues(new[] { "One", "Two" }));
_unknownMultipleValueResponseHeaders.Append("Two", new StringValues(new[] { "One", "Two" }));
_unknownMultipleValueResponseHeaders.Append("Three", new StringValues(new[] { "One", "Two" }));
_unknownMultipleValueResponseHeaders.Append("Four", new StringValues(new[] { "One", "Two" }));
_enumerator = new Http2HeadersEnumerator();
}
[Benchmark]
public void KnownSingleValueResponseHeaders()
{
_enumerator.Initialize(_knownSingleValueResponseHeaders);
if (_enumerator.MoveNext())
{
}
}
[Benchmark]
public void KnownMultipleValueResponseHeaders()
{
_enumerator.Initialize(_knownMultipleValueResponseHeaders);
if (_enumerator.MoveNext())
{
}
}
[Benchmark]
public void UnknownSingleValueResponseHeaders()
{
_enumerator.Initialize(_unknownSingleValueResponseHeaders);
if (_enumerator.MoveNext())
{
}
}
[Benchmark]
public void UnknownMultipleValueResponseHeaders()
{
_enumerator.Initialize(_unknownMultipleValueResponseHeaders);
if (_enumerator.MoveNext())
{
}
}
}
}