-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Caching common HttpResults types #40965
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
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
if ( statusCode is (< 100) or (> 511)) | |
if (statusCode is (< 100) or (> 511)) |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's fine to use t4.
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.
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.
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.
Yes, @mhutch owns this
[GeneratedCode("TextTemplatingFileGenerator", "")] | ||
internal partial class ResultsCache | ||
{ | ||
private static StatusCodeHttpResult? _status100Continue; |
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.
Should these be static readonly
s
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 StatusCodeHttpResult
s 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ew, but OK 😄
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.
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.
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.
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 <[email protected]>
|
||
internal static StatusCodeHttpResult StatusCode(int statusCode) | ||
{ | ||
if (statusCode is (< 100) or (> 599)) |
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.
if (statusCode is (< 100) or (> 599)) | |
if (statusCode is (< 101) or (> 599)) |
Co-authored-by: Brennan <[email protected]>
|
||
/// <summary> | ||
/// Produces a <see cref="StatusCodes.Status404NotFound"/> response. | ||
/// </summary> | ||
/// <param name="value">The value to be included in the HTTP response body.</param> | ||
/// <returns>The created <see cref="IResult"/> for the response.</returns> | ||
public static IResult NotFound(object? value = null) | ||
=> new NotFoundObjectHttpResult(value); | ||
=> value is null ? ResultsCache.NotFound : new NotFoundObjectHttpResult(value); | ||
|
||
/// <summary> | ||
/// Produces a <see cref="StatusCodes.Status401Unauthorized"/> response. |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 int
s not IResult
s.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
partial
classResultsCache
that contains the following commonHttpResults
cached instances:NoContentHttpResult
: HTTP 204UnauthorizedHttpResult
: HTTP 401OkObjectHttpResult
: HTTP 200 **BadRequestObjectHttpResult
: HTTP 400 **NotFoundObjectHttpResult
: HTTP 404 **ConflictObjectHttpResult
: HTTP 409 **UnprocessableEntityObjectHttpResult
: HTTP 422 **** with
null
contentIt also includes an auto generated (
T4 text template
) addition to this new partial class that will handle caching forStatusCodeHttpResult
. This cache will use a simpleswitch
expression 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 ns
or2 ns
that might not affect most of the scenarios. Also, as expected, the cache will avoid the continuousStatusCodeHttpResult
object 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
StatusCodeHttpResult
caching 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)