Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

[WIP] Ported HighPerformanceCounter internal API from CoreRt #27175

Closed
wants to merge 2 commits into from

Conversation

VSadov
Copy link
Member

@VSadov VSadov commented Oct 14, 2019

There is no access to a high precision timer in corlib, which is very limiting for time-sensitive features in managed code.
CoreRt HighPerformanceCounter is lean, yet seems sufficient for most needs.

@VSadov VSadov requested a review from jkotas October 14, 2019 05:49
@@ -0,0 +1,56 @@
// Licensed to the .NET Foundation under one or more agreements.
Copy link
Member

@jkotas jkotas Oct 14, 2019

Choose a reason for hiding this comment

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

This is very weak test (e.g. compared to how we test stop watch). I do not think you need to bother with a test like this.

Copy link
Member Author

Choose a reason for hiding this comment

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

The API basically exposes QueryPerformanceCounter and QueryPerformanceFequency.
I am just testing that they are connected. - not crashing with missing methods, etc...

Copy link
Member Author

Choose a reason for hiding this comment

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

There is not a lot of functionality. I just could not do it without minimal testing to see that API works at all.

Copy link
Member

@jkotas jkotas Oct 14, 2019

Choose a reason for hiding this comment

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

The longer term value of such testing is super low. You will get the same level of testing by any use of this.

Copy link
Member Author

Choose a reason for hiding this comment

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

I figured - if I didn't have tests, I'd be asked "why not a single test was added? how do we know it works?". I myself would feel uncomfortable.
Removing a test is easy though.

internal static class HighPerformanceCounter
{
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern ulong QueryPerformanceFrequency();
Copy link
Member

Choose a reason for hiding this comment

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

It would be nice to wait for @AaronRobinsonMSFT #26458 to go through and the implement it using that.

Copy link
Member Author

Choose a reason for hiding this comment

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

It looks like #26458 is about PInvoking to external libraries. I need to call corlib since it shims these methods for multiple platforms (Linux, OSX).

I am wondering if #26458 will help this case.

Copy link
Member

Choose a reason for hiding this comment

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

It would use corefx shims, like CoreRT and Stopwatch does today.

Copy link
Member Author

Choose a reason for hiding this comment

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

Coreclr needs these APIs and will have to shim for any new platform added. Why not just use that?

Copy link
Member

Choose a reason for hiding this comment

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

The code written against CoreFX shims is shareable between CoreCLR, Mono and Corert. We are trying to maximize amount of such shared code,

Copy link
Member Author

Choose a reason for hiding this comment

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

Also #26458 seems to be inserting GCPoll in every call. Is that a good idea for a high performance counter?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, I think we need to start working towards more predictable GC suspension schemes to support GC SLAs. Many FCalls are missing the GC poll today - it is a bug.

Copy link
Member Author

@VSadov VSadov Oct 14, 2019

Choose a reason for hiding this comment

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

Just to be clear - I am not arguing against #26458 in general. I like it and would use it in many other places.
But for high performance counter having as little overhead as possible is important. FCall is the best option that we have for low overhead. I think it is more justified here than in many other places where FCall is used.

Copy link
Member

@jkotas jkotas Oct 14, 2019

Choose a reason for hiding this comment

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

FCalls without a GC poll are also a reliable way to stall the GC. It is not hard to come up with FCall-heavy microbenchmarks that have GC suspension times 0.5 seconds and more.

The two instructions one pays for the GC poll is absolutely worth paying to mitigate this problem. I expect that we may enable the GC poll across the board for all FCalls once we start being serious about GC SLAs.

Note you have to pay the cost of the API wrapper with FCall. #26458 lets you call the OS API directly (on Windows at least), and so it is going to faster than what you got here, even with GC poll.

{
return QueryPerformanceCounter();
}
}
Copy link
Member

@stephentoub stephentoub Oct 14, 2019

Choose a reason for hiding this comment

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

What is the benefit of this as compared to Stopwatch.GetTickCount(), just that it's not in corelib?

This represents the bulk of the Stopwatch implementation. If we really need it in corelib, then shouldn't we just move Stopwatch down rather than duplicating it?

And if we must duplicate it, can't we use the code (modified as necessary) in the shared partition?

I also don't like us adding code like this that's not actually used.

Copy link
Member Author

Choose a reason for hiding this comment

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

Stopwatch is not in corlib, yes. It was recommended to look at CoreRt for how it deals with that. CoreRt has this. I am not sure why not Stopwatch. I assumed there are reasons for CoreRt having this API instead and also apply here. Like layering for additional features/diagnostics used there, or maybe just desire/rules to keep extension APIs out of corlib when possible.
Surely the same questions came up for CoreRt and were resolved once already.

I would be using this in an otherwise unrelated work. I did not want to bundle it all together. Also to manage risk of finding that the change is undesirable before investing more in this direction.

I kind of expected that this would be an uncontroversial simple change though, since I heard that not having HP timers in corlib was a problem in the past. And again - by looking at prior art in CoreRt.

Copy link
Member

Choose a reason for hiding this comment

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

was recommended to look at CoreRt for how it deals with that.

I have mentioned both CoreRT and Stopwatch as examples for how to deal with that. Both use regular PInvokes with signatures defined in shared partition. Neither of these two have duplicated FCalls.

I assumed there are reasons for CoreRt having this API.

We used to have a lot of duplication between CoreLib and CoreFX due to historical artificial layering. We do not have this artificial layering anymore and we prefer to avoid duplication.

@jkotas
Copy link
Member

jkotas commented Oct 14, 2019

Moving the whole Stopwatch to CoreLib (without any modifications) is probably the best way to achieve what you are looking for.

@VSadov
Copy link
Member Author

VSadov commented Oct 14, 2019

Moving the whole Stopwatch to CoreLib (without any modifications)

That is a bigger more disruptive change. Before embarking on that we'd better be in agreement that we want such change.

@stephentoub - do you see problems with that?
I.E: public API layering, compat, ref assemblies, reflection use, diagnostics, documenting, localization, build breaks when stuff is in both places etc.

Such move was probably done before. What are the typical traps to look for?

Do we want to move the tests too?

@VSadov VSadov changed the title Ported HighPerformanceCounter internal API from CoreRt [WIP] Ported HighPerformanceCounter internal API from CoreRt Oct 14, 2019
@stephentoub
Copy link
Member

do you see problems with that?

I'm fine with that... actually called it out earlier: "This represents the bulk of the Stopwatch implementation. If we really need it in corelib, then shouldn't we just move Stopwatch down rather than duplicating it?" 😉

In the past to avoid issues we've staged it something like this:

  • PR in corefx to move Stopwatch to src\Common\src\Corelib.
  • Wait for mirror bot to open PR to bring changes to coreclr.
  • Update Corelib to include Stopwatch.
  • Wait for bot to open PR in corefx bringing updated build of corelib with Stopwatch
  • Add a commit to that PR which removes Stopwatch.*.cs from System.Runtime.Extensions build.

Ref and tests should remain in corefx.

@VSadov
Copy link
Member Author

VSadov commented Oct 14, 2019

Thanks @stephentoub !!

Ref and tests should remain in corefx.

I was particularly unsure what happens to these. Sounds good.

@VSadov
Copy link
Member Author

VSadov commented Oct 14, 2019

So I will close this PR then.

The Stopwatch move will need to be initiated from the CoreFx side.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants