Improve HTTP/2 headers enumerator performance (#24726)
This commit is contained in:
parent
da6aa6fc6a
commit
ca1505ba3b
|
|
@ -11,11 +11,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
{
|
||||
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 HttpResponseTrailers.Enumerator _trailersEnumerator;
|
||||
private IEnumerator<KeyValuePair<string, StringValues>> _genericEnumerator;
|
||||
private StringValues.Enumerator _stringValuesEnumerator;
|
||||
private bool _hasMultipleValues;
|
||||
private KnownHeaderType _knownHeaderType;
|
||||
|
||||
public int HPackStaticTableId => GetResponseHeaderStaticTableId(_knownHeaderType);
|
||||
|
|
@ -29,136 +36,89 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
|||
public void Initialize(HttpResponseHeaders headers)
|
||||
{
|
||||
_headersEnumerator = headers.GetEnumerator();
|
||||
_trailersEnumerator = default;
|
||||
_genericEnumerator = null;
|
||||
_isTrailers = false;
|
||||
|
||||
_stringValuesEnumerator = default;
|
||||
Current = default;
|
||||
_knownHeaderType = default;
|
||||
_headersType = HeadersType.Headers;
|
||||
_hasMultipleValues = false;
|
||||
}
|
||||
|
||||
public void Initialize(HttpResponseTrailers headers)
|
||||
{
|
||||
_headersEnumerator = default;
|
||||
_trailersEnumerator = headers.GetEnumerator();
|
||||
_genericEnumerator = null;
|
||||
_isTrailers = true;
|
||||
|
||||
_stringValuesEnumerator = default;
|
||||
Current = default;
|
||||
_knownHeaderType = default;
|
||||
_headersType = HeadersType.Trailers;
|
||||
_hasMultipleValues = false;
|
||||
}
|
||||
|
||||
public void Initialize(IDictionary<string, StringValues> headers)
|
||||
{
|
||||
_headersEnumerator = default;
|
||||
_trailersEnumerator = default;
|
||||
_genericEnumerator = headers.GetEnumerator();
|
||||
_isTrailers = false;
|
||||
|
||||
_stringValuesEnumerator = default;
|
||||
Current = default;
|
||||
_knownHeaderType = default;
|
||||
_headersType = HeadersType.Untyped;
|
||||
_hasMultipleValues = false;
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (MoveNextOnStringEnumerator())
|
||||
if (_hasMultipleValues && MoveNextOnStringEnumerator(Current.Key))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
return MoveNextOnStringEnumerator();
|
||||
}
|
||||
|
||||
private string GetCurrentKey()
|
||||
{
|
||||
if (_genericEnumerator != null)
|
||||
else if (_headersType == HeadersType.Trailers)
|
||||
{
|
||||
return _genericEnumerator.Current.Key;
|
||||
}
|
||||
else if (_isTrailers)
|
||||
{
|
||||
return _trailersEnumerator.Current.Key;
|
||||
return _trailersEnumerator.MoveNext()
|
||||
? SetCurrent(_trailersEnumerator.Current.Key, _trailersEnumerator.Current.Value, _trailersEnumerator.CurrentKnownType)
|
||||
: false;
|
||||
}
|
||||
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();
|
||||
Current = result ? new KeyValuePair<string, string>(GetCurrentKey(), _stringValuesEnumerator.Current) : default;
|
||||
Current = result ? new KeyValuePair<string, string>(key, _stringValuesEnumerator.Current) : default;
|
||||
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())
|
||||
{
|
||||
enumerator = default;
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
}
|
||||
Current = new KeyValuePair<string, string>(name, value.ToString());
|
||||
_knownHeaderType = knownHeaderType;
|
||||
_hasMultipleValues = false;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_headersEnumerator.MoveNext())
|
||||
{
|
||||
enumerator = default;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
enumerator = _headersEnumerator.Current.Value.GetEnumerator();
|
||||
_knownHeaderType = _headersEnumerator.CurrentKnownType;
|
||||
return true;
|
||||
}
|
||||
_stringValuesEnumerator = value.GetEnumerator();
|
||||
_hasMultipleValues = true;
|
||||
|
||||
return MoveNextOnStringEnumerator(name);
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
if (_genericEnumerator != null)
|
||||
if (_headersType == HeadersType.Headers)
|
||||
{
|
||||
_genericEnumerator.Reset();
|
||||
_headersEnumerator.Reset();
|
||||
}
|
||||
else if (_isTrailers)
|
||||
else if (_headersType == HeadersType.Trailers)
|
||||
{
|
||||
_trailersEnumerator.Reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
_headersEnumerator.Reset();
|
||||
_genericEnumerator.Reset();
|
||||
}
|
||||
_stringValuesEnumerator = default;
|
||||
_knownHeaderType = default;
|
||||
|
|
|
|||
|
|
@ -50,18 +50,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
[Fact]
|
||||
public void CanIterateOverResponseTrailers()
|
||||
{
|
||||
var responseHeaders = new HttpResponseTrailers
|
||||
var responseTrailers = new HttpResponseTrailers
|
||||
{
|
||||
ContentLength = 9,
|
||||
HeaderETag = "ETag!"
|
||||
};
|
||||
responseHeaders.Append("Name1", "Value1");
|
||||
responseHeaders.Append("Name2", "Value2-1");
|
||||
responseHeaders.Append("Name2", "Value2-2");
|
||||
responseHeaders.Append("Name3", "Value3");
|
||||
responseTrailers.Append("Name1", "Value1");
|
||||
responseTrailers.Append("Name2", "Value2-1");
|
||||
responseTrailers.Append("Name2", "Value2-2");
|
||||
responseTrailers.Append("Name3", "Value3");
|
||||
|
||||
var e = new Http2HeadersEnumerator();
|
||||
e.Initialize(responseHeaders);
|
||||
e.Initialize(responseTrailers);
|
||||
|
||||
var headers = GetNormalizedHeaders(e);
|
||||
|
||||
|
|
@ -75,6 +75,58 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
|||
}, 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)
|
||||
{
|
||||
var headers = new List<KeyValuePair<string, string>>();
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue