Caching common HttpResults types#40965
Conversation
|
|
||
| internal static StatusCodeHttpResult StatusCode(int statusCode) | ||
| { | ||
| if ( statusCode is (< 100) or (> 511)) |
There was a problem hiding this comment.
| if ( statusCode is (< 100) or (> 511)) | |
| if (statusCode is (< 100) or (> 511)) |
There was a problem hiding this comment.
For reference: Perf-goodness will come for free with dotnet/roslyn#60534
|
|
||
| internal static StatusCodeHttpResult StatusCode(int statusCode) | ||
| { | ||
| if ( statusCode is (< 100) or (> 511)) |
There was a problem hiding this comment.
| if ( statusCode is (< 100) or (> 511)) | |
| if (statusCode is (< 100) or (> 511)) |
|
|
||
| internal static partial class ResultsCache | ||
| { | ||
| public static NotFoundObjectHttpResult NotFound { get; } = new(null); |
There was a problem hiding this comment.
Out of curiosity, why not align this with the other status code results? You generally don't need to be concerned with back-compat of implementation particularly when all of it was previously non-public.
There was a problem hiding this comment.
why not align this with the other status code results?
I did not follow, sorry, what do you mean about it?
There was a problem hiding this comment.
@pranavkm As of right now, we've decided to avoid a type hierarchy. And I don't think we want NotFound(object? value = null) returning a StatusCodeHttpResult in some cases and a NotFoundObjectHttpResult in others.
| @@ -0,0 +1,108 @@ | |||
| <#@ template debug="true" hostspecific="true" language="C#" #> | |||
There was a problem hiding this comment.
I imagine this limits you to having to use VS to regen the file. Have you considered doing something different (e.g. https://github.com/dotnet/aspnetcore/tree/main/src/Servers/Kestrel/tools/CodeGenerator) which works everywhere? Alternatively now that the generated code is available, use that to drive further work and not check this in?
There was a problem hiding this comment.
If I am not wrong it is already supported in Rider and probably should have some VS Code extension available but I could check the alternative that you mentioned.
There was a problem hiding this comment.
Also, a quick search let me find the Mono.TextTemplating project https://github.com/mono/t4. That is a community dotnet tool that allows the transformation of the T4 templates.
| [GeneratedCode("TextTemplatingFileGenerator", "")] | ||
| internal partial class ResultsCache | ||
| { | ||
| private static StatusCodeHttpResult? _status100Continue; |
There was a problem hiding this comment.
Should these be static readonlys
| private static StatusCodeHttpResult? _status100Continue; | |
| private static readonly StatusCodeHttpResult? s_status100Continue = new(StatusCodes.Status100Continue); |
And in the switch below
return statusCode switch
{
StatusCodes.Status100Continue => s_status100Continue,
...That way the lazy-initialization is shifted to the runtime, and by beforefieldinit it's done on first access.
With tiered-compilation hot StatusCodeHttpResults could already be initialized, so no extra (runtime-) check is needed anymore.
Otherwise it's pretty the same if there's a ?? new(StatusCodes.XYZ) or if that check is done by the runtime (I guess).
Of course the change in the tt-file below.
There was a problem hiding this comment.
@gfoidl I really appreciate your comment. I did not try the tiered-compilation thing yet however the beforefieldinit indeed do a lazy-initialization but based on my tests when the initialization happens all the ~ 60 fields are initialized and all memory is allocated. The performance improvement is minimum compared with the null check approach.
Initial allocation cost (First time access)
| Method | Gen 0 | Gen 1 | Allocated |
|---|---|---|---|
| StaticCacheWithSwitchExpression | 0.0095 | - | 2,000 B |
| DynamicCacheWithSwitchExpression | 0.0001 | - | 24 B |
| Method | Mean | Error | StdDev | Gen 0 | Allocated |
|---|---|---|---|---|---|
| StaticCacheWithSwitchExpression | 1.867 ns | 0.0045 ns | 0.0042 ns | - | - |
| DynamicCacheWithSwitchExpression | 1.889 ns | 0.0143 ns | 0.0119 ns | - | - |
There was a problem hiding this comment.
Drop _status100Continue, that's a response code that's only ever returned by the server, not the app.
|
|
||
| <ItemGroup> | ||
| <!-- This is the T4 template service and is added by VS anytime you modify a T4 template. Required for .tt files. --> | ||
| <Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" /> |
There was a problem hiding this comment.
I don't like it too, and I can actually remove but it will come back, and the next dev will need to remove it again. If that is fine i would prefer to remove it, including the Compile/None items added by VS.
halter73
left a comment
There was a problem hiding this comment.
If it's easy enough, it'd be nice to add a test to make sure the source and T4 template don't go out of sync. And please add something to the src/Http/README.md how to rerun the T4 template using the dotnet-t4 tool for people without VS.
@halter73 I added the unit tests and updated the readme. can you please take a quick look? |
Co-authored-by: Günther Foidl <gue@korporal.at>
|
|
||
| internal static StatusCodeHttpResult StatusCode(int statusCode) | ||
| { | ||
| if (statusCode is (< 100) or (> 599)) |
There was a problem hiding this comment.
| if (statusCode is (< 100) or (> 599)) | |
| if (statusCode is (< 101) or (> 599)) |
Co-authored-by: Brennan <brecon@microsoft.com>
| => value is null ? ResultsCache.NotFound : new NotFoundObjectHttpResult(value); | ||
|
|
||
| /// <summary> | ||
| /// Produces a <see cref="StatusCodes.Status401Unauthorized"/> response. |
There was a problem hiding this comment.
Doesn't this actually return a cached UnauthorizedHttpResult instance❔ Same for other special cases in src/Http/Http.Results/src/ResultsCache.cs e.g. the NotFoundObjectHttpResult just above.
There was a problem hiding this comment.
Sorry, I am confusing about your question. This will return a cached NotFoundObjectHttpResult not UnauthorizedHttpResult. Maybe I am missing something.
There was a problem hiding this comment.
My comment is about the <summary/> after the overload that returns NotFoundObjectHttpResult. I probably should have commented on that earlier overload.
In any case, I was confused and thought the <summary/> was about the return type. StatusCodes.Status404NotFound, StatusCodes.Status401Unauthorized, et cetera are of course ints not IResults.
In retrospect, my suggestion should have been to make the return type (NotFoundObjectHttpResult, UnauthorizedHttpResult, et cetera) clear as well.
There was a problem hiding this comment.
I agree about the return type but that is a problem that we have right now because this class was shipped already but we are working on it #41009
There was a problem hiding this comment.
My suggestion was about the doc comments, not the method signature e.g.
| /// Produces a <see cref="StatusCodes.Status401Unauthorized"/> response. | |
| /// Returns an <see cref="UnauthorizedHttpResult"/> that produces a <see cref="StatusCodes.Status401Unauthorized"/> response. |
Again, this is a suggestion and could be done later. You're not otherwise changing return types or updating <summary/> elements in this PR.

In this PR is introduced a new
partialclassResultsCachethat contains the following commonHttpResultscached instances:NoContentHttpResult: HTTP 204UnauthorizedHttpResult: HTTP 401OkObjectHttpResult: HTTP 200 **BadRequestObjectHttpResult: HTTP 400 **NotFoundObjectHttpResult: HTTP 404 **ConflictObjectHttpResult: HTTP 409 **UnprocessableEntityObjectHttpResult: HTTP 422 **** with
nullcontentIt also includes an auto generated (
T4 text template) addition to this new partial class that will handle caching forStatusCodeHttpResult. This cache will use a simpleswitchexpression with lazy object allocation based on the benchmarks listed below.In general, the benchmarks shown that an improvement in the execution time when a known status code but a degradation when an unknown status code, however, the difference is around
1 nsor2 nsthat might not affect most of the scenarios. Also, as expected, the cache will avoid the continuousStatusCodeHttpResultobject allocation (24 bytes) that happens every request.Fix #39951
Micro-benchmark results
All types used for the benchmarks are listed here: https://gist.github.com/brunolins16/82593d2f24ae63d602baa902b09a5ed3
Also, all benchmarks listed here are related to the
StatusCodeHttpResultcaching only.Initial allocation cost
Known status code (Eg. HTTP 200)
Unknown status code (Eg. HTTP 150)
Status code outside of the documented range (100..599) (Eg. HTTP 900)