Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HttpResponseHeaders.CopyToFast move directly to next rather than if branching #32337

Conversation

benaadams
Copy link
Member

@benaadams benaadams commented May 2, 2021

Can use BitOperations.TrailingZeroCount on the set bits to determine which is the next header; rather than testing each bit in sequence via fall-through, droping x37 if statements from the CopyToFast response header output method.

Second commit is pulling Content-Length to the first header in tests (as its a special long? outlier header)

from

case 5: // Header: "Accept-Ranges"
    if ((tempBits & 0x10L) != 0) // Test
    {
        tempBits ^= 0x10L;
        values = ref _headers._AcceptRanges;
        keyStart = 48;
        keyLength = 17;
        next = 6;
        break; // OutputHeader
    }
    goto case 6; // Fall-through

case 6: // Header: "Access-Control-Allow-Credentials"
    if ((tempBits & 0x20L) != 0) // Test
    {
        tempBits ^= 0x20L;
        values = ref _headers._AccessControlAllowCredentials;
        keyStart = 65;
        keyLength = 36;
        next = 7;
        break; // OutputHeader
    }
    goto case 7; // Fall-through

to

case 4: // Header: "Accept-Ranges"
    values = ref _headers._AcceptRanges;
    keyStart = 48;
    keyLength = 17;
    break; // OutputHeader

case 5: // Header: "Access-Control-Allow-Credentials"
    values = ref _headers._AccessControlAllowCredentials;
    keyStart = 65;
    keyLength = 36;
    break; // OutputHeader

735 bytes less IL

- // IL Code size 2325 (0x915)
+ // IL Code size 1590 (0x636)

871 bytes less asm

- ; Total bytes of asm code: 3910
+ ; Total bytes of asm code: 3039
Branch Type Mean Op/s Delta
main TechEmpowerPlaintext 56.79 ns 17,609,767.6 -
PR TechEmpowerPlaintext 53.53 ns 18,679,950.2 +6.1%
main PlaintextChunked 61.89 ns 16,158,595.4 -
PR PlaintextChunked 47.82 ns 20,912,547.5 +29.4%
main PlaintextWithCookie 133.94 ns 7,466,115.3 -
PR PlaintextWithCookie 126.26 ns 7,920,045.3 +6.1%
main Plain(...)ookie [26] 163.29 ns 6,124,234.5 -
PR Plain(...)ookie [26] 168.58 ns 5,932,007.3 -3.1%
main LiveAspNet 359.96 ns 2,778,117.3 -
PR LiveAspNet 354.01 ns 2,824,802.3 +1.6%
main Common 766.48 ns 1,304,672.7 -
PR Common 721.08 ns 1,386,812.9 +6.3%

@ghost ghost added area-runtime community-contribution Indicates that the PR has been added by a community member labels May 2, 2021
@benaadams benaadams changed the title Jump to next Header rather using fall through if branching HttpResponseHeaders.CopyToFast move directly to next rather than if branching May 2, 2021
@benaadams benaadams force-pushed the Headers-jump-to-next-rather-than-fall-through-if-branching branch 3 times, most recently from 4591a6c to 5b87ba3 Compare May 2, 2021 21:52
@benaadams benaadams marked this pull request as ready for review May 2, 2021 21:53
@benaadams benaadams force-pushed the Headers-jump-to-next-rather-than-fall-through-if-branching branch 2 times, most recently from 36c9824 to 1d6783d Compare May 3, 2021 04:02
@benaadams
Copy link
Member Author

/azp run

@azure-pipelines
Copy link

Commenter does not have sufficient privileges for PR 32337 in repo dotnet/aspnetcore

@Tratcher
Copy link
Member

Tratcher commented May 3, 2021

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 2 pipeline(s).

@benaadams
Copy link
Member Author

Looks like CI has its own issues

@benaadams benaadams force-pushed the Headers-jump-to-next-rather-than-fall-through-if-branching branch 2 times, most recently from 5de29f2 to 3cd78db Compare May 8, 2021 13:42
Copy link
Member

@JamesNK JamesNK left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CopyToFast is used by HTTP/1.1, but HTTP/2 uses the generated Enumerator types when encoding HPack response headers.

Can this improvement be applied to how the generated Enumerator types decide next? Writing response headers in HTTP/2 is a hotspot in our current implementation. Multiplexing and HPack means that only one response can write headers at a time while other responses wait on a lock. Reducing the time in the lock is huge.

@benaadams
Copy link
Member Author

Can this improvement be applied to how the generated Enumerator types decide next?

Yes, though would do it in follow up

@benaadams benaadams force-pushed the Headers-jump-to-next-rather-than-fall-through-if-branching branch from 3cd78db to 7609129 Compare June 7, 2021 21:36
@benaadams
Copy link
Member Author

Rebased for merge conflict

@JamesNK
Copy link
Member

JamesNK commented Jun 7, 2021

Can this improvement be applied to how the generated Enumerator types decide next?

Yes, though would do it in follow up

#33352

@benaadams benaadams force-pushed the Headers-jump-to-next-rather-than-fall-through-if-branching branch from 7609129 to aaa9f22 Compare June 8, 2021 00:27
@benaadams
Copy link
Member Author

Rebased to retrigger glitchy CI

@JamesNK
Copy link
Member

JamesNK commented Jun 9, 2021

I've rerun the build a couple of times and it is consistently failing. The Windows build is timing out.

@BrennanConroy
Copy link
Member

BrennanConroy commented Jun 9, 2021

D:\workspace_work\1\s\eng\targets\CSharp.Common.targets(115,5): error MSB5021: Terminating the task executable "cmd" and its child processes because the build was canceled. [D:\workspace_work\1\s\src\Servers\Kestrel\perf\Microbenchmarks\Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks.csproj]
SUCCESS: The process with PID 5864 (child process of PID 8004) has been terminated.
SUCCESS: The process with PID 8004 (child process of PID 2648) has been terminated.

Microbenchmarks validation is likely hanging. Something changed to make a single run of one or more of the the benchmarks hang.

'InMemoryTransportBenchmark.Plaintext: Dry(Toolchain=InProcessNoEmitToolchain, IterationCount=1, LaunchCount=1, RunStrategy=ColdStart, UnrollFactor=1, WarmupCount=1), 0 runs' failed, reason: 'AllMeasurements'

@Tratcher
Copy link
Member

Tratcher commented Jun 29, 2021

In hung_dotnet.exe_2596_210608_031244.dmp I see a thread hung at ConsoleLifetime.OnProcessExit waiting for the host be be disposed.
https://github.com/dotnet/runtime/blob/6f7f30e118d6652a89dd7261e31d33ecd75f66fe/src/libraries/Microsoft.Extensions.Hosting/src/Internal/ConsoleLifetime.cs#L79-L86

The hung process has a base directory of D:\workspace_work\1\s\artifacts\bin\Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks\Release\net6.0\0

Main thread callstack:

 	ntdll.dll!NtWaitForMultipleObjects�()	Unknown
 	KERNELBASE.dll!WaitForMultipleObjectsEx()	Unknown
 	[Inline Frame] hostpolicy.dll!coreclr_t::shutdown(int *) Line 132	C++
 	hostpolicy.dll!run_app_for_context(const hostpolicy_context_t & context, int argc, const wchar_t * * argv) Line 264	C++
 	hostpolicy.dll!run_app(const int argc, const wchar_t * * argv) Line 284	C++
 	hostpolicy.dll!corehost_main(const int argc, const wchar_t * * argv) Line 430	C++
 	hostfxr.dll!execute_app(const std::wstring & impl_dll_dir, corehost_init_t * init, const int argc, const wchar_t * * argv) Line 146	C++
 	hostfxr.dll!`anonymous namespace'::read_config_and_execute(const std::wstring & host_command, const host_startup_info_t & host_info, const std::wstring & app_candidate, const std::unordered_map<enum known_options,std::vector<std::wstring,std::allocator<std::wstring>>,known_options_hash,std::equal_to<enum known_options>,std::allocator<std::pair<enum known_options const ,std::vector<std::wstring,std::allocator<std::wstring>>>>> & opts, int new_argc, const wchar_t * * new_argv, host_mode_t mode, wchar_t * out_buffer, int buffer_size, int * required_buffer_size) Line 520	C++
 	hostfxr.dll!fx_muxer_t::handle_exec_host_command(const std::wstring & host_command, const host_startup_info_t & host_info, const std::wstring & app_candidate, const std::unordered_map<enum known_options,std::vector<std::wstring,std::allocator<std::wstring>>,known_options_hash,std::equal_to<enum known_options>,std::allocator<std::pair<enum known_options const ,std::vector<std::wstring,std::allocator<std::wstring>>>>> & opts, int argc, const wchar_t * * argv, int argoff, host_mode_t mode, wchar_t * result_buffer, int buffer_size, int * required_buffer_size) Line 1001	C++
 	hostfxr.dll!fx_muxer_t::execute(const std::wstring host_command, const int argc, const wchar_t * * argv, const host_startup_info_t & host_info, wchar_t * result_buffer, int buffer_size, int * required_buffer_size) Line 566	C++
>	hostfxr.dll!hostfxr_main_startupinfo(const int argc, const wchar_t * * argv, const wchar_t * host_path, const wchar_t * dotnet_root, const wchar_t * app_path) Line 61	C++
 	dotnet.exe!exe_start(const int argc, const wchar_t * * argv) Line 236	C++
 	dotnet.exe!wmain(const int argc, const wchar_t * * argv) Line 305	C++
 	[Inline Frame] dotnet.exe!invoke_main() Line 90	C++
 	dotnet.exe!__scrt_common_main_seh() Line 288	C++
 	kernel32.dll!BaseThreadInitThunk�()	Unknown
 	ntdll.dll!RtlUserThreadStart�()	Unknown

InMemoryTransportBenchmark looks like the only benchmark that creates a host. It does dispose it in GlobalCleanup, but maybe that's not happening or its failing?

_host = new HostBuilder()
.ConfigureWebHost(webHostBuilder =>
{
webHostBuilder
// Prevent VS from attaching to hosting startup which could impact results
.UseSetting("preventHostingStartup", "true")
.UseKestrel()
// Bind to a single non-HTTPS endpoint
.UseUrls("http://127.0.0.1:5000")
.Configure(app => app.UseMiddleware<PlaintextMiddleware>());
})
.ConfigureServices(services => services.AddSingleton<IConnectionListenerFactory>(transportFactory))
.Build();
_host.Start();

[GlobalCleanup]
public void GlobalCleanup()
{
_host.Dispose();
}

@Tratcher Tratcher force-pushed the Headers-jump-to-next-rather-than-fall-through-if-branching branch from 062eb9e to 24b4ede Compare June 29, 2021 21:04
@Tratcher Tratcher assigned Tratcher and unassigned halter73 Jun 29, 2021
@Tratcher
Copy link
Member

Tratcher commented Jun 29, 2021

Rebased, dropped the submodule changes, and added a commit that should help avoid the hang. Looking to get this merged to avoid conflicts with #33776.

@BrennanConroy
Copy link
Member

Any idea why the hang started showing up with this change?

@Tratcher
Copy link
Member

Any idea why the hang started showing up with this change?

No, it' wasn't even in an affected benchmark.

@Tratcher
Copy link
Member

Nevermind, I found it:

The benchmark is throwing an exception related to this change (Content-Length ordering changed), and then GlobalCleanup doesn't run.

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
 ---> System.AggregateException: One or more errors occurred. (Invalid response
Expected:
HTTP/1.1 200 OK
Date: Fri, 02 Mar 2018 18:37:05 GMT
Content-Type: text/plain
Server: Kestrel
Content-Length: 13

Hello, World!
Actual:
HTTP/1.1 200 OK
Content-Length: 13
Content-Type: text/plain
Date: Tue, 29 Jun 2021 22:01:42 GMT
Server: Kestrel

Hello, World!)
 ---> System.InvalidOperationException: Invalid response
Expected:
HTTP/1.1 200 OK
Date: Fri, 02 Mar 2018 18:37:05 GMT
Content-Type: text/plain
Server: Kestrel
Content-Length: 13

Hello, World!
Actual:
HTTP/1.1 200 OK
Content-Length: 13
Content-Type: text/plain
Date: Tue, 29 Jun 2021 22:01:42 GMT
Server: Kestrel

Hello, World!
   at Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks.InMemoryTransportBenchmark.ValidateResponseAsync(Byte[] request, String expectedResponse) in D:\github\AspNetCore\src\Servers\Kestrel\perf\Microbenchmarks\InMemoryTransportBenchmark.cs:line 79
   --- End of inner exception stack trace ---
   at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions) in System.Private.CoreLib.dll:token 0x6002df0+0x11
   at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken) in System.Private.CoreLib.dll:token 0x6002e0c+0x3f
   at Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks.InMemoryTransportBenchmark.GlobalSetupPlaintext() in D:\github\AspNetCore\src\Servers\Kestrel\perf\Microbenchmarks\InMemoryTransportBenchmark.cs:line 62
   at BenchmarkDotNet.Engines.EngineFactory.CreateReadyToRun(EngineParameters engineParameters) in BenchmarkDotNet.dll:token 0x60009a7+0x0
   at BenchmarkDotNet.Autogenerated.Runnable_1.Run(BenchmarkCase benchmarkCase, IHost host) in RefEmit_InMemoryManifestModule:token 0x600002a+0x26
   --- End of inner exception stack trace ---
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Span`1& arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) in System.Private.CoreLib.dll:token 0x6004f5c+0x23
   at BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation.RunnableProgram.Run(BenchmarkId benchmarkId, Assembly partitionAssembly, BenchmarkCase benchmarkCase, IHost host) in BenchmarkDotNet.dll:token 0x600030b+0x0

@Tratcher Tratcher force-pushed the Headers-jump-to-next-rather-than-fall-through-if-branching branch from 24b4ede to fb34819 Compare June 29, 2021 22:08
@JamesNK
Copy link
Member

JamesNK commented Jun 29, 2021

Is there a way to make benchmark errors easier to debug? A friendly error message would be much better than an unidentified freeze.

@BrennanConroy
Copy link
Member

I'm not sure if there is, but in most cases it's easy enough to run the benchmark in VS and see the issue

@Tratcher Tratcher merged commit 4517992 into dotnet:main Jun 30, 2021
@ghost ghost added this to the 6.0-preview7 milestone Jun 30, 2021
@Tratcher
Copy link
Member

Thanks @benaadams

@amcasey amcasey added area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions and removed area-runtime labels Jun 6, 2023
@github-actions github-actions bot locked and limited conversation to collaborators Dec 8, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions community-contribution Indicates that the PR has been added by a community member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants