Allocations in FormReader #553
Description
There's a lot of overhead right now in FormReader
Data is from 3000 requests to https://github.com/aspnet/Performance/tree/dev/testapp/BigModelBinding (x64). The client is doing a form post of about 100 form fields - we kinda consider this the upper bound for the amount of data (and shape of data) that you'd want to put through MVC model binding.
Based on this profile, my napkin math shows about 77mb of allocations from our underlying representation (string
+ System.Collections.Generic.Dictionary<String, StringValues>
+ etc) and about 122mb coming from various overheads.
I'm excluding from the 122mb stuff that's in theory covered by @benaadams current work (byte/char buffers, etc).
Breaking this down further
Task<string>
- 47mb
Task<Nullable<KeyValuePair<string, string>>>
- 28mb
Dictionary<string, List<String>>
, List<string>
, string[]
- 25mb 12mb 9mb
Scratch data used in KeyValueAccumulator
to build the final Dictionary<string, StringValues>
Note that 5mb of string[]
allocations are coming from a hashset in MVC, so I excluded it from this total
Some thoughts
We should consider a design for FormReader
where we pass the accumulator around or store values in fields/properties instead of returning them via Task<T>
. Using async
plus Task<T>
at such a chatty level is what causes all of this overhead. Using Task
on the other hand can avoid this issue.
If we're in a state where we know the entire body is buffered in memory, we might want to just optimize that path to do a synchronous read. That's probably a simpler fix, and it will result in running much more efficient code without any async overhead.
We should also consider changing KeyValueAccumulator
to just operate on Dictionary<string, StringValues
directly. Having multiple values for the same fields is the less-common case. If we wanted to be smart about it, we could basically build List<T>
's resizing behavior into a StringValues.Add
method (returns a StringValues
since they are immutable), and then all cases would be pretty optimal.
Right now we're making the worst case the common case by allocating a List<string>
and string[]
for the common case of a single value per key.