-
Notifications
You must be signed in to change notification settings - Fork 10.1k
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
perf: improve allocations in OwinEnvironment
#58917
base: main
Are you sure you want to change the base?
perf: improve allocations in OwinEnvironment
#58917
Conversation
TODO:
|
ebd529e
to
ebc7e4d
Compare
concept looks solid, nice; added some thoughts |
could you please resolve conflict @DeagleGross |
@@ -119,4 +121,40 @@ bool IDictionary<string, StringValues>.TryGetValue(string key, out StringValues | |||
value = default(StringValues); | |||
return false; | |||
} | |||
|
|||
public struct Enumerator : IEnumerator<KeyValuePair<string, StringValues>>, IEnumerator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can this not share as public struct Enumerator : IEnumerator<KeyValuePair<string, T>>, IEnumerator
with the one above? ConvertingEnumerator or something
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
renamed to ConvertingEnumerator
. I dont think I can make a generic impl for inner enumerator of DictionaryStringArrayWrapper
and DictionaryStringValuesWrapper
. That was your idea, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'd have to pass in a Convert delegate to reuse the same one. Maybe not worth the bother.
OwinEnvironment allocates a
Dictionary<string, FeatureMap>
with at least 23 entries ofstring
andFeatureMap
objects per request.In most cases, Owin abstraction is used only to get the data in the specific format (i.e. access the HTTP method via
OwinConstants.RequestMethod
key), but not to remove \ add entries or rebuild the wholeFeatureMap
dictionary.Therefore I am introducing the
OwinEntries
class (not visible to users), which allocates the static readonly entries dictionary (similar to what existed before) and uses that for the key-value access (so a single allocation per app lifetime against the per-request allocation). However,OwinEnvironment
has a rich API to modify the dictionary (i.e. remove entries or clear them completely). Therefore I am doing the following to fully support existing API and dont introduce breaking changes:OwinEnvironment.FeatureMaps
returningIDictionary<string, FeatureMap>
there is no way to securely determine if the dictionary instance will be changed, and because of that we can't avoid performing the deep-copy of static_entries
(same perf loss as existed). Next interaction withOwinEnvironment
will be using_contextEntries
(request-lifetime) instead of static_entries
.OwinEnvironment.Remove(string key)
I am using a separateHashSet<string> _deletedKeys
to keep track of deleted entries per request lifetime. Even if all original entries are deleted, this is still a more lightweight flow than existed beforeOwinEnvironment.Clear()
I am falling back to_contextEntries
usage (request-lifetime)Note: there are some
FeatureMap
objects, which are dependent on theHttpContext
passed intoOwinEnvironment
, so I keep them separately in a dedicatedDictionary<string, FeatureMap>
. It's contains a single entry so far.I have added the microbenchmark (see PR), with a code that performs multiple requests using the default
HttpContext
, and used the newOwinEnvironment
implementation against the old one:Benchmark results:
(thanks to @deanward81 for the idea)
Closes #58916