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>>
|
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;
|
||||||
|
|
|
||||||
|
|
@ -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>>();
|
||||||
|
|
|
||||||
|
|
@ -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