Skip to content
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

Merged
merged 4 commits into from
Mar 29, 2021

Conversation

gfoidl
Copy link
Member

@gfoidl gfoidl commented Mar 28, 2021

@dotnet-issue-labeler
Copy link

Note regarding the new-api-needs-documentation label:

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.

@ghost
Copy link

ghost commented Mar 28, 2021

Tagging subscribers to this area: @maryamariyan
See info in area-owners.md if you want to be subscribed.

Issue Details

Fixes #45290

/cc: @davidfowl @KalleOlaviNiemitalo

Author: gfoidl
Assignees: -
Labels:

area-Extensions-Logging, new-api-needs-documentation

Milestone: -

return (logger, exception) =>
{
if (logger.IsEnabled(logLevel))
{
logger.Log(logLevel, eventId, new LogValues(formatter), exception, LogValues.Callback);
Log(logger, exception);
Copy link
Member

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?

Copy link
Member Author

@gfoidl gfoidl Mar 28, 2021

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);
    }
}

Copy link
Member

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 😢

Copy link
Member Author

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 😄

Copy link
Member

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

Copy link
Member

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?

Copy link
Member

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;
}

Copy link
Member

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

Copy link
Member Author

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.

Copy link
Member

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?

Copy link
Member

@davidfowl davidfowl left a comment

Choose a reason for hiding this comment

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

LGTM

@davidfowl davidfowl merged commit 3b0c4e4 into dotnet:main Mar 29, 2021
@davidfowl
Copy link
Member

thanks @gfoidl, stellar work as usual!

@maryamariyan
Copy link
Member

maryamariyan commented Apr 1, 2021

@gfoidl we forgot to add tests for the new APIs. Would you be open to adding a PR for the code coverage?

@gfoidl
Copy link
Member Author

gfoidl commented Apr 2, 2021

Sure, will address this over the weekend.

@stephentoub
Copy link
Member

stephentoub commented Apr 10, 2021

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?

@davidfowl
Copy link
Member

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

@davidfowl
Copy link
Member

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.

@stephentoub
Copy link
Member

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?

@davidfowl
Copy link
Member

#48619

@ghost ghost locked as resolved and limited conversation to collaborators May 10, 2021
@karelz karelz added this to the 6.0.0 milestone May 20, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add LoggerMessage.Define overload to disable IsEnabled check
6 participants