Skip to content

perf: improve allocations in OwinEnvironment #58917

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

Merged

Conversation

DeagleGross
Copy link
Member

@DeagleGross DeagleGross commented Nov 13, 2024

OwinEnvironment allocates a Dictionary<string, FeatureMap> with at least 23 entries of string and FeatureMap 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 whole FeatureMap 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:

  1. for API OwinEnvironment.FeatureMaps returning IDictionary<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 with OwinEnvironment will be using _contextEntries (request-lifetime) instead of static _entries.
  2. for API OwinEnvironment.Remove(string key) I am using a separate HashSet<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 before
  3. for API OwinEnvironment.Clear() I am falling back to _contextEntries usage (request-lifetime)

Note: there are some FeatureMap objects, which are dependent on the HttpContext passed into OwinEnvironment, so I keep them separately in a dedicated Dictionary<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 new OwinEnvironment implementation against the old one:

    [Benchmark]
    public async Task ProcessMultipleRequests()
    {
        foreach (var i in Enumerable.Range(0, 10000))
        {
            await _requestDelegate(_httpContext);
        }
    }

Benchmark results:

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
OwinRequest_NoOperation (old) 27.46 ms 0.508 ms 0.821 ms 2750.0000 62.5000 - 133 MB
OwinRequest_AccessPorts (old) 29.45 ms 0.577 ms 0.931 ms 2750.0000 62.5000 - 133 MB
OwinRequest_AccessHeaders (old) 28.52 ms 0.565 ms 1.344 ms 2781.2500 93.7500 - 133 MB
OwinRequest_NoOperation (fix) 6.162 ms 0.1208 ms 0.1984 ms 234.3750 - - 12 MB
OwinRequest_AccessPorts (fix) 6.388 ms 0.1219 ms 0.1304 ms 234.3750 - - 12 MB
OwinRequest_AccessHeaders (fix) 6.478 ms 0.0921 ms 0.0817 ms 250.0000 - - 12 MB

(thanks to @deanward81 for the idea)

Closes #58916

@DeagleGross DeagleGross self-assigned this Nov 13, 2024
@ghost ghost added the area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions label Nov 13, 2024
@DeagleGross
Copy link
Member Author

DeagleGross commented Nov 13, 2024

TODO:

  • include DictionaryStringValuesWrapper and DictionaryStringArrayWrapper enumerator improvements
  • improve allocations for port string conversion

@DeagleGross DeagleGross requested review from wtgodbe and a team as code owners November 14, 2024 11:30
@DeagleGross DeagleGross force-pushed the dmkorolev/owin/environment-allocations branch from ebd529e to ebc7e4d Compare November 14, 2024 11:35
@mgravell
Copy link
Member

concept looks solid, nice; added some thoughts

@danmoseley
Copy link
Member

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
Copy link
Member

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

Copy link
Member Author

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?

Copy link
Member

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.

@dotnet-policy-service dotnet-policy-service bot added the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Dec 3, 2024
@danmoseley danmoseley requested a review from Copilot February 14, 2025 04:03
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot reviewed 5 out of 12 changed files in this pull request and generated 2 comments.

Files not reviewed (7)
  • AspNetCore.sln: Language not supported
  • src/Http/HttpAbstractions.slnf: Language not supported
  • src/Http/Owin/benchmarks/Microsoft.AspNetCore.Owin.Microbenchmarks/Microsoft.AspNetCore.Owin.Microbenchmarks.csproj: Language not supported
  • src/Http/samples/MinimalSampleOwin/MinimalSampleOwin.csproj: Language not supported
  • src/Http/samples/MinimalSampleOwin/Properties/launchSettings.json: Language not supported
  • src/Http/Owin/src/DictionaryStringValuesWrapper.cs: Evaluated as low risk
  • src/Http/Owin/src/DictionaryStringArrayWrapper.cs: Evaluated as low risk

return _contextEntries;
}

public bool TryGetValue(string key, out FeatureMap entry)
Copy link
Preview

Copilot AI Feb 14, 2025

Choose a reason for hiding this comment

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

The method does not correctly handle the case where _contextEntries is not initialized and _deletedKeys contains the key. Add a check for _deletedKeys before returning false when _contextEntries is not initialized.

Copilot uses AI. Check for mistakes.

return _entries.ContainsKey(key) || _contextDependentEntries.ContainsKey(key);
}

public bool Remove(string key)
Copy link
Preview

Copilot AI Feb 14, 2025

Choose a reason for hiding this comment

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

The method does not correctly handle the case where _contextEntries is not initialized and _deletedKeys contains the key. Add a check for _deletedKeys before returning false when _contextEntries is not initialized.

Copilot uses AI. Check for mistakes.

@DeagleGross
Copy link
Member Author

/azp run

Copy link

Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

@dotnet-policy-service dotnet-policy-service bot removed the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label May 9, 2025
@DeagleGross DeagleGross merged commit fd3fe12 into dotnet:main May 13, 2025
27 checks passed
@DeagleGross DeagleGross deleted the dmkorolev/owin/environment-allocations branch May 13, 2025 08:55
@dotnet-policy-service dotnet-policy-service bot added this to the 10.0-preview5 milestone May 13, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions Perf
Projects
None yet
Development

Successfully merging this pull request may close these issues.

perf: improve allocations in OwinEnvironment
5 participants