-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Added LoggerMessage.Define overloads to disable IsEnabled check #50334
Conversation
Note regarding the This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, to please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change. |
Tagging subscribers to this area: @maryamariyan Issue DetailsFixes #45290 /cc: @davidfowl @KalleOlaviNiemitalo
|
return (logger, exception) => | ||
{ | ||
if (logger.IsEnabled(logLevel)) | ||
{ | ||
logger.Log(logLevel, eventId, new LogValues(formatter), exception, LogValues.Callback); | ||
Log(logger, exception); |
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'm assuming the codegen differences are negligible?
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.
The methods in the closure / display-class look like
internal void <Define>b__1(ILogger logger, Exception? exception)
{
<Define>g__Log|0(logger, exception);
}
internal void <Define>b__2(ILogger logger, Exception? exception)
{
if (logger.IsEnabled(logLevel))
{
<Define>g__Log|0(logger, exception);
}
}
internal void <Define>g__Log|0(ILogger logger, Exception? exception)
{
logger.Log(logLevel, eventId, new LogValues(formatter), exception, LogValues.Callback);
}
so the JIT should inline <Define>g__Log|0
and in effect the codegen be the same.
Add aggressive inlining?
Before it was
internal void <Define>b__0(ILogger logger, Exception? exception)
{
if (logger.IsEnabled(logLevel))
{
logger.Log(logLevel, eventId, new LogValues(formatter), exception, LogValues.Callback);
}
}
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 figured it should inline. I was gonna check but https://sharplab.io/ is down 😢
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.
Wanted to do the same, unfortunately...we're too dependent on sharplab 😄
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.
We all need to sponsor @ashmind
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.
Grrr. Can you manually inline then?
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.
Does it change if you do this:
if (skipEnabledCheck)
{
return Log;
}
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.
Lets change this to return the Log method directly for the skipEnabledCheck
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.
Done with 3e547f1
The other branch (!skipEnabledCheck) is left as is. Codegen (from above) is the check for IsEnabled
then the tail-call. I assume together with PGO machine-code layout will be good, so there should be no perf-penalty.
On local micro-benchmarks (with a Null-Logger) I couldn't see any difference between calling Log(...)
local function or manually inlining.
With the suggested change to return Log
directly for the skipEnabledCheck the IL size is smaller too and the indirection for the tail-call is gone.
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 did you find micro benchmark use cases for which adding a skipEnabledCheck would have improved performance?
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.
LGTM
So that it is (manually) inlined. Cf. dotnet#50334 (comment)
thanks @gfoidl, stellar work as usual! |
@gfoidl we forgot to add tests for the new APIs. Would you be open to adding a PR for the code coverage? |
Sure, will address this over the weekend. |
What is the cost of the check this is skipping? Is it not just a compare and a branch (or is it also skipping an extra interface dispatch)? Are there benchmarks that show these overloads are worth it? |
Delegate invocations can't be inlined. So the cost is a delegate invocation unless an explicit check is added on the outside. This just removes the redundant check when IsEnabled is being used |
Also FWIW, the logging system needs to ask multiple providers if IsEnabled is true. It isn't a simple check. First there's a filtering at the logger factory level and then it needs to call into each provider to ask it if it's enabled. That's up to n interface dispatches per call. Repeating it is just redundant when you know you're going to check it before the call. |
Thanks. Is there a typical example as would show up in a real app that highlights these costs well? Do we have benchmarks around this somewhere? |
Fixes #45290
/cc: @davidfowl @KalleOlaviNiemitalo