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

Add new APIs for abstracting the system clock and local time zone. #48681

Closed

Conversation

mattjohnsonpint
Copy link
Contributor

@mattjohnsonpint mattjohnsonpint commented Feb 24, 2021

This PR is a proposed implementation of the revised API proposal I submitted for #36617. I do not expect this PR to be merged at this time. Please see the issue thread for details.

One open question I have with specific regards to the implementation (not the public API)

  • In TimeContext.cs, I implement the internal storage for the ambient context simply as:

    private static readonly AsyncLocal<TimeContext> s_context = new();

    This seems to work fine, and all tests pass. However, looking at the implementation for a similar concept, I see that CultureInfo.CurrentCulture and CultureInfo.CurrentUICulture each use both an AsyncLocal<T> field and a [ThreadStatic] field, with some logic synchronizing them. Is that necessary here also? Why or why not?

Thanks.

Benchmarks

I ran the existing microbenchmarks from here, that test DateTime.UtcNow, DateTimeOffset.UtcNow, DateTime.Now, and DateTimeOffset.Now, both before and after my changes, and both with and without leap seconds enabled (via registry flag as described here). The benchmark results show roughly a 8-20ns cost for the UtcNow properties, and a 19-92ns cost for the Now properties, both of which I believe are reasonable and negligible.

Full benchmark results follow.

ResultsComparer difference, with leap seconds enabled

Slower diff/base Base Median (ns) Diff Median (ns) Modality
System.Tests.Perf_DateTime.GetUtcNow 1.23 84.10 103.49 bimodal
System.Tests.Perf_DateTimeOffset.GetUtcNow 1.14 87.15 99.20
System.Tests.Perf_DateTime.GetNow 1.41 225.26 317.44 bimodal
System.Tests.Perf_DateTimeOffset.GetNow 1.09 266.76 291.16

ResultsComparer difference, with leap seconds disabled

Slower diff/base Base Median (ns) Diff Median (ns) Modality
System.Tests.Perf_DateTime.GetUtcNow 1.29 29.12 37.46
System.Tests.Perf_DateTimeOffset.GetUtcNow 1.44 33.19 47.91
System.Tests.Perf_DateTime.GetNow 1.37 172.73 236.09 several?
System.Tests.Perf_DateTimeOffset.GetNow 1.09 214.23 232.70

Test Setup

BenchmarkDotNet=v0.12.1.1466-nightly, OS=Windows 10.0.19042
Intel Core i7-6600U CPU 2.60GHz (Skylake), 1 CPU, 4 logical and 2 physical cores
.NET SDK=6.0.100-preview.2.21120.3
  [Host]     : .NET 6.0.0 (6.0.21.11806), X64 RyuJIT
  Job-YNAJFG : .NET 6.0.0 (42.42.42.42424), X64 RyuJIT

PowerPlanMode=00000000-0000-0000-0000-000000000000  Arguments=/p:DebugType=portable  Toolchain=CoreRun  
IterationTime=250.0000 ms  MaxIterationCount=20  MinIterationCount=15  
WarmupCount=1  

Before changes, leap seconds enabled

Benchmark Test Class: System.Tests.Perf_DateTime

Method Mean Error StdDev Median Min Max Gen 0 Gen 1 Gen 2 Allocated
GetNow 224.61 ns 9.719 ns 10.80 ns 225.26 ns 200.93 ns 248.0 ns - - - -
GetUtcNow 88.22 ns 9.425 ns 10.85 ns 84.10 ns 75.37 ns 108.9 ns - - - -

Benchmark Test Class: System.Tests.Perf_DateTimeOffset

Method Mean Error StdDev Median Min Max Gen 0 Gen 1 Gen 2 Allocated
GetNow 264.16 ns 7.296 ns 8.403 ns 266.76 ns 248.45 ns 276.40 ns - - - -
GetUtcNow 87.16 ns 0.460 ns 0.384 ns 87.15 ns 86.67 ns 88.07 ns - - - -

After changes, leap seconds enabled

Benchmark Test Class: System.Tests.Perf_DateTime

Method Mean Error StdDev Median Min Max Gen 0 Gen 1 Gen 2 Allocated
GetNow 318.6 ns 17.70 ns 19.67 ns 317.4 ns 295.43 ns 371.8 ns 0.0300 - - 64 B
GetUtcNow 102.3 ns 12.35 ns 14.22 ns 103.5 ns 81.61 ns 126.0 ns - - - -

Benchmark Test Class: System.Tests.Perf_DateTimeOffset

Method Mean Error StdDev Median Min Max Gen 0 Gen 1 Gen 2 Allocated
GetNow 290.33 ns 7.544 ns 8.072 ns 291.16 ns 274.91 ns 309.7 ns - - - -
GetUtcNow 98.69 ns 2.394 ns 2.757 ns 99.20 ns 93.35 ns 102.8 ns - - - -

Before changes, leap seconds disabled

Benchmark Test Class: System.Tests.Perf_DateTime

Method Mean Error StdDev Median Min Max Gen 0 Gen 1 Gen 2 Allocated
GetNow 181.71 ns 18.316 ns 21.093 ns 172.73 ns 155.07 ns 228.21 ns - - - -
GetUtcNow 28.67 ns 0.906 ns 0.930 ns 29.12 ns 26.47 ns 29.91 ns - - - -

Benchmark Test Class: System.Tests.Perf_DateTimeOffset

Method Mean Error StdDev Median Min Max Gen 0 Gen 1 Gen 2 Allocated
GetNow 215.47 ns 3.810 ns 4.235 ns 214.23 ns 208.70 ns 224.72 ns - - - -
GetUtcNow 32.98 ns 1.020 ns 1.092 ns 33.19 ns 30.72 ns 34.82 ns - - - -

After changes, leap seconds disabled

Benchmark Test Class: System.Tests.Perf_DateTime

Method Mean Error StdDev Median Min Max Gen 0 Gen 1 Gen 2 Allocated
GetNow 235.03 ns 8.502 ns 8.731 ns 236.09 ns 218.86 ns 255.13 ns 0.0302 - - 64 B
GetUtcNow 37.37 ns 1.473 ns 1.696 ns 37.46 ns 34.88 ns 40.53 ns - - - -

Benchmark Test Class: System.Tests.Perf_DateTimeOffset

Method Mean Error StdDev Median Min Max Gen 0 Gen 1 Gen 2 Allocated
GetNow 231.58 ns 8.161 ns 8.380 ns 232.70 ns 214.52 ns 247.20 ns - - - -
GetUtcNow 47.59 ns 1.233 ns 1.319 ns 47.91 ns 43.86 ns 49.18 ns - - - -

@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.

@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this PR. If you have write-permissions please help me learn by adding exactly one area label.

@mattjohnsonpint
Copy link
Contributor Author

mattjohnsonpint commented Feb 24, 2021

@davidfowl
Copy link
Member

The proposed API needs to go through a couple of phases before it gets to the PR stage.

@tarekgh
Copy link
Member

tarekgh commented Feb 24, 2021

@mattjohnsonpint in #36617 it was discussed that we don't want to allow mocking APIs in the core and that should be supported in the extensions if we need to after getting rid of duplicates. So, I don't think we'll accept such changes before finalizing the discussion.

I am adding here the folks who attended the design review for this issue: @GrabYourPitchforks @terrajobst @bartonjs

CC @maryamariyan

@tarekgh tarekgh added the NO-MERGE The PR is not ready for merge yet (see discussion for detailed reasons) label Feb 24, 2021
@mattjohnsonpint
Copy link
Contributor Author

Agreed about needing to go through more discussion and phases. I provide the PR so you could see how such an API might be implemented. We'll continue accordingly.

On the point of mocking, please see my comments in the issue thread. Thanks.

@stephentoub
Copy link
Member

Thanks. Closing the PR as the commits can be viewed without needing a PR open.

@tarekgh
Copy link
Member

tarekgh commented Feb 24, 2021

The benchmark results show roughly a 8-20ns cost for the UtcNow properties, and a 19-92ns cost for the Now properties, both of which I believe are reasonable and negligible.

If I am reading the benchmark correctly, it will be non-trivial regression. we have been working to enhance the perf of the Now/UtcNow trying to get every gain as possible. The regression percentage is not trivial. don't look at the time units but look at the percentage and consider the scenarios that the app is calling Now/UtcNow more frequent. We are trying to think in ways to enhance the performance and such change would make that difficult.

@davidfowl
Copy link
Member

I think this API is important but I'm not a fan of the async local usage

@mattjohnsonpint
Copy link
Contributor Author

@tarekgh, I saw several things that could improve perf while I was working on this. Some are in here, but several were unrelated to this feature. I can send a separate PR for those things if you like.

Generally agree about the perf. Faster is always better. Not sure that a few nanoseconds difference is really all that much though. I did find it odd that the benchmarks showed the DateTimeOffset.UtcNow test faster (by percent) with leap seconds enabled, given that's the slower path. I seemed to get quite a bit of variation in the benchmarks, even though my system was as quiet as possible at the time. Perhaps they don't do well at such small scales? Not sure.

@stephentoub
Copy link
Member

I can send a separate PR for those things if you like.

Please do 😄

@mattjohnsonpint
Copy link
Contributor Author

Would love to find a better way than AsyncLocal also. I couldn't think of a better way that could affect the results from existing APIs. Seemed like the right approach, since it's used by things like CultureInfo.CurrentCulture. I'm open to suggestions.

@ghost ghost locked as resolved and limited conversation to collaborators Mar 26, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
new-api-needs-documentation NO-MERGE The PR is not ready for merge yet (see discussion for detailed reasons)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants