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

LoggerMessage formatter performance improvements #58524

Closed
wants to merge 1 commit into from
Closed

LoggerMessage formatter performance improvements #58524

wants to merge 1 commit into from

Conversation

Scooletz
Copy link

@Scooletz Scooletz commented Sep 1, 2021

Remark: This is still a draft, but before I spend a lot of time, I'd appreciate a soft yay or nay from people responsible for the logging area. It will take more time and it'd be great if I could get some initial feedback. The initials numbers are amazing, and being given that this PR targets LoggerMessage, which should be high-perf, this looks like a potential huge gain for all the loggers calling the formatter.


This PR is a result of profiling/benchmarking a piece of code that touched Durable Functions and used ApplicationInsight. I found that the logger, that is provided as the official package, calls formatter, having the state not only logged as usual key-value pairs but also as a formatted string.

https://github.com/microsoft/ApplicationInsights-dotnet/blob/d54bae8aeaf74b6fa08c8033c2ce54ea6d8f437b/LOGGING/src/ILogger/ApplicationInsightsLogger.cs#L113

https://github.com/microsoft/ApplicationInsights-dotnet/blob/d54bae8aeaf74b6fa08c8033c2ce54ea6d8f437b/LOGGING/src/ILogger/ApplicationInsightsLogger.cs#L94

The idea behind this PR would be to improve the formatter built by LoggerMessage by capturing much more context during the build up of LogValuesFormatter and to use it when formatting the value. I'm aware that LogValues should be kept small (big structs are no-no for perf) and this can be amended later on by capturing the formatting info into a separate object.

Benchmarks

Initial benchmarks, even with some functionality missing, looks extremely promising. Both the time and the allocations (no boxing!) are reduced

BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19042.1165 (20H2/October2020Update)
Intel Core i7-6700HQ CPU 2.60GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
.NET SDK=6.0.100-preview.7.21379.14
  [Host]     : .NET 6.0.0 (6.0.21.37719), X64 RyuJIT
  DefaultJob : .NET 6.0.0 (6.0.21.37719), X64 RyuJIT

Method Mean Error StdDev Median Ratio RatioSD Gen 0 Allocated
'int32 logged with the template: "A value {Value}"' 113.28 ns 2.267 ns 2.425 ns 113.77 ns 1.00 0.00 0.0204 64 B
new 'int32 logged with the template: "A value {Value}"' 43.73 ns 0.847 ns 0.976 ns 43.61 ns 0.39 0.01 0.0127 40 B
'Guid logged with the template: "A value {Value}"' 149.22 ns 2.930 ns 3.488 ns 149.04 ns 1.00 0.00 0.0458 144 B
new 'Guid logged with the template: "A value {Value}"' 85.79 ns 1.723 ns 1.984 ns 86.04 ns 0.58 0.02 0.0356 112 B
'int32 logged with the template: "A value {Value} was used"' 146.91 ns 5.435 ns 15.419 ns 142.06 ns 1.00 0.00 0.0279 88 B
new 'int32 logged with the template: "A value {Value} was used"' 50.99 ns 1.024 ns 2.069 ns 50.44 ns 0.34 0.04 0.0204 64 B
'Guid logged with the template: "A value {Value} was used"' 176.59 ns 4.941 ns 13.855 ns 175.78 ns 1.00 0.00 0.0508 160 B
new 'Guid logged with the template: "A value {Value} was used"' 104.11 ns 4.137 ns 12.002 ns 102.65 ns 0.60 0.09 0.0408 128 B

@ghost ghost added the community-contribution Indicates that the PR has been added by a community member label Sep 1, 2021
@ghost
Copy link

ghost commented Sep 1, 2021

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

Issue Details

Remark: This is still a draft, but before I spend a lot of time, I'd appreciate a soft yay or nay from people responsible for the logging area. It will take more time and it'd be great if I could get some initial feedback.


This PR is a result of profiling/benchmarking a piece of code that touched Durable Functions and used ApplicationInsights. I found that the logger that is provided as the official package calls formatter, having the state not only logged as usual key-value pairs but also as a formatted string.

https://github.com/microsoft/ApplicationInsights-dotnet/blob/d54bae8aeaf74b6fa08c8033c2ce54ea6d8f437b/LOGGING/src/ILogger/ApplicationInsightsLogger.cs#L113

https://github.com/microsoft/ApplicationInsights-dotnet/blob/d54bae8aeaf74b6fa08c8033c2ce54ea6d8f437b/LOGGING/src/ILogger/ApplicationInsightsLogger.cs#L94

The idea behind this PR would be to improve the formatter built by LoggerMessage by capturing much more context during the build up of LogValuesFormatter and to use it when formatting the value.

Benchmarks

Initial benchmarks, even with some functionality missing, looks extremely promising. Both the time and the allocations (no boxing!) are reduced

BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19042.1165 (20H2/October2020Update)
Intel Core i7-6700HQ CPU 2.60GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
.NET SDK=6.0.100-preview.7.21379.14
  [Host]     : .NET 6.0.0 (6.0.21.37719), X64 RyuJIT
  DefaultJob : .NET 6.0.0 (6.0.21.37719), X64 RyuJIT

Method Mean Error StdDev Median Ratio RatioSD Gen 0 Allocated
'int32 logged with the template: "A value {Value}"' 113.28 ns 2.267 ns 2.425 ns 113.77 ns 1.00 0.00 0.0204 64 B
new 'int32 logged with the template: "A value {Value}"' 43.73 ns 0.847 ns 0.976 ns 43.61 ns 0.39 0.01 0.0127 40 B
'Guid logged with the template: "A value {Value}"' 149.22 ns 2.930 ns 3.488 ns 149.04 ns 1.00 0.00 0.0458 144 B
new 'Guid logged with the template: "A value {Value}"' 85.79 ns 1.723 ns 1.984 ns 86.04 ns 0.58 0.02 0.0356 112 B
'int32 logged with the template: "A value {Value} was used"' 146.91 ns 5.435 ns 15.419 ns 142.06 ns 1.00 0.00 0.0279 88 B
new 'int32 logged with the template: "A value {Value} was used"' 50.99 ns 1.024 ns 2.069 ns 50.44 ns 0.34 0.04 0.0204 64 B
'Guid logged with the template: "A value {Value} was used"' 176.59 ns 4.941 ns 13.855 ns 175.78 ns 1.00 0.00 0.0508 160 B
new 'Guid logged with the template: "A value {Value} was used"' 104.11 ns 4.137 ns 12.002 ns 102.65 ns 0.60 0.09 0.0408 128 B
Author: Scooletz
Assignees: -
Labels:

area-Extensions-Logging, community-contribution

Milestone: -

@maryamariyan maryamariyan self-requested a review September 2, 2021 18:20
@maryamariyan maryamariyan added this to the 7.0.0 milestone Sep 2, 2021
@Scooletz
Copy link
Author

Scooletz commented Sep 3, 2021

@maryamariyan Thank you for chiming in. Looking forward for your opinion on whether this is something we want to pursue. This will require a lot of polishing, but the initial idea is in there. If you need any more input, ping me. I'm looking forward to hearing your opinion.

@Scooletz
Copy link
Author

Scooletz commented Sep 9, 2021

Any thoughts/opinions @maryamariyan ?

@Scooletz
Copy link
Author

Could I get an update on this or ETA where I can expect some input whether to push forward with this?

@Scooletz
Copy link
Author

It's been 20 days since a request to get some input. Could sb chime in and let me know whether this is something that has a possibility to be merged in? I don't want to propose sth to get it rejected.

@danmoseley
Copy link
Member

It should not take this long.
@dotnet/area-extensions-logging @maryamariyan

Copy link
Member

@eerhardt eerhardt left a comment

Choose a reason for hiding this comment

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

I think this is an interesting idea. It may make sense pursue this more. It would be good to get the CI passing so we know that the change doesn't affect behavior.

Thoughts @davidfowl @pakrym ?

@@ -540,8 +585,24 @@ public LogValues(LogValuesFormatter formatter, T0 value0)
}
}

public override string ToString()
{
Span<char> output = stackalloc char[1024];
Copy link
Member

Choose a reason for hiding this comment

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

How do we know 1024 is big enough?

Copy link
Member

Choose a reason for hiding this comment

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

This should use ValueStringBuilder

Copy link
Author

Choose a reason for hiding this comment

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

It's not. This was just to please some initial tests and I'll need to amend it in a much smarter way 😄

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Append<TValue>(Span<char> span, TValue value, string format)
{
// ReSharper disable once MergeCastWithTypeCheck
Copy link
Member

Choose a reason for hiding this comment

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

What are these comments for?

Copy link
Author

Choose a reason for hiding this comment

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

Pleasing Resharper. Will remove.

private static int Append<TValue>(Span<char> span, TValue value, string format)
{
// ReSharper disable once MergeCastWithTypeCheck
if (value is ISpanFormattable)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (value is ISpanFormattable)
if (value is ISpanFormattable spanFormattable)

Copy link
Author

Choose a reason for hiding this comment

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

I believe I was falling with having it JITted without boxing. It was a while ago. Will take a look at it and use the new syntax if possible.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, today the JIT can't avoid boxing when using if (value is ISpanFormattable sf) { sf.TryFormat(...); } but it can when doing if (value is ISpanFormattable) { ((ISpanFormattable)value).TryFormat(...); }; the latter case is easier to prove correct.

@Scooletz
Copy link
Author

Thx for chiming in @eerhardt . If this is looks like an initially interesting idea let me give it a spin and make CI green. Also, if there are some more opinions about it, let me know.

@stephentoub
Copy link
Member

In #50330 we discussed using the proposed CompositeFormat as an implementation detail under LoggerMessage.Define. Is this essentially doing the same thing?

@Scooletz
Copy link
Author

I wasn't aware of this beautiful proposal @stephentoub ! Actually, the case that I'm raising here, is mentioned in the original repo https://github.com/geeknoid/FastFormatting#template-based-format-strings ! Having this said, I'm closing this PR as the mentioned functionality is set to Future milestone. Logging will get this lunch for free 😅

@Scooletz Scooletz closed this Sep 23, 2021
@Scooletz Scooletz deleted the LoggerMessage-ToString-optimized branch September 23, 2021 15:17
@ghost ghost locked as resolved and limited conversation to collaborators Nov 3, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-Extensions-Logging community-contribution Indicates that the PR has been added by a community member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants