dotnet / dotnet/aspnetcore

Inefficiency when reading large request bodies (e.g. uploads)

Open
#32,467 25 comments 2 reactions 1 assignee Claimed by @davidfowl View on GitHub
area-networking investigate Perf
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 10h
Merged PRs (30d)
281

Description

Consider an ASP.NET MVC action dealing with file uploads like below.
```csharp
using var destination = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, 81920);
await Request.Body.CopyToAsync(destination, 81920);
```
The code above uses 80KB buffer for the reasons mentioned here https://github.com/dotnet/runtime/blob/release/5.0/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs#L33.

The code above will use about 3 or 4 times more CPU resources than the same code using .NET Framework System.Web. This is comparing ``CopyToAsync`` in .NET Core with ``CopyTo`` (synchronous) in .NET Framework. The async version used a lot of CPU in .NET Framework as well, but resorting to synchronous operations solved that with similar or better upload speeds. We can't (by default) use the synchronous version in .NET Core, though, and even when doing so by setting ``AllowSynchronousIO``, it uses about the same amount of CPU resources as the async version.

I'm wondering if this might have to do with the pipe reader in .NET Core reading in 4KB chunks, compared to 16KB chunks in .NET Framework and System.Web.

I profiled the code while uploading a little more than half a GB of data. I can see that calls to ``FileStream.WriteCore`` indicate that the buffer size of 80KB is honored. I get around 6 500 writes (512MB / 80KB). The number of request body stream reads is a lot higher, though, and indicate a 4KB buffer limit causing overhead.

When running under IIS, the number of calls to ``Microsoft.AspNetCore.Server.IIS.NativeMethods.http_read_request_bytes`` is over 120 000 in my example and that is reflected in the number of thread-pool dispatch related calls.

I can reproduce the same issue with Kestrel, but with slightly different call stacks for reading the request body, of course. I haven't found a way of configuring the default buffer size of the pipe readers used.

Using ``FileOptions.Asynchronous`` when opening the file stream doesn't seem to make a lot of difference.

This issue could be related to dotnet/runtime#43480, although they are discussing general performance of pipes and in relation to writing the response.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.