-
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
Improvements for Delegate.GetHashCode() for CsWinRT (and NativeAOT) #96947
Comments
Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas Issue DetailsOverviewWe're working on improving the binary size of apps/libraries using CsWinRT and NativeAOT, and talking with @MichalStrehovsky we noticed that the new trimming optimization to stop delegate targets from being considered reflection-visible by default (#96166) is not kicking in at all, as it's blocked by the use of We need to calculate a "good" pseudo-random value for a given delegate, which will be the starting value for an event registration token passed to the WinRT side. Currently we're using runtime/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs Lines 151 to 154 in 46d3b27
The way we're working around that is to check if the delegate is unicast, and in that case do Dropping that I don't have an exact ask here, so opening this more to try to figure out what the best approach to this could be. For instance, would it be possible to change the namespace System.Runtime.CompilerServices;
public static class RuntimeHelpers
{
public static int GetDelegateHashCode(Delegate? d);
} This could compute a "better", slightly more expensive hashcode, that would combine all target methods as well. To also try things from another angle — @AaronRobinsonMSFT @jkoritzinsky I know you have experience in this area specifically (I mean WinRT event handling in general). Would you know whether it might be possible to actually just stop this approach entirely and not just, for instance, use a completely random value for the lower 32 bits instead? Reading all the comments in CsWinRT it's not really clear why that's not already being done. The comments say the current approach "will also give us a shot at preventing unregistration rom becoming an O(N) operation", but given the unregistration is just performed with a token that's coming from outside this class (ie. the one that callers would've stored when registering), it's not clear to me at all how this would relate to how that initial token value was calculated, and especially to whether it was a pseudo-random value depending on the exact delegate instance or not. To clarify: the goal here is to drop that Also cc. @manodasanW
|
Is this a fixed cost or does it grow with size of application? Put another way, how does the 200 KB grow relative to application size.
I'd be leary of this change simply due to the fact that I could easily construct a scenario where users have taken an indirect dependency on
I think calling this "hash code" is something we should avoid. Something akin to
Honestly, I don't recall. This is a question best for @Scottj1s or @jkoritzinsky. |
The cost is not fixed, but it will grow linearly with the number of delegates being used (as in, including with different instances of the same delegate types, pointing to different methods). The fact CsWinRT touches that
Yeah that makes sense. A separate API seems like a good solution there, plus it makes this completely pay-for-play.
Just spitballing here:
I kinda like |
I do not understand why this needs to be this complicated. What is not going to work well if this method is replaced by a random number?
BTW: This has a redundant call of |
That is not entirely clear to me either, and part of why I opened this issue hoping someone with more experience in this area in particular could shed some light on this. We found some old branch in the built-in WinRT support that was just using I did think about using
Yup, noticed that too yesterday! Will open a PR to drop that. Was thinking we can actually skip the second lookup entirely on .NET 6+ and just use a single EDIT: opened microsoft/CsWinRT#1445 to fix that. |
Do we know about any issues that were diagnosed thanks to the current encoding scheme? I understand that encoding the delegate target breadcrumbs can help in theory, but it is still fairly non-trivial to use this breadcrumb to diagnose issues with dangling or corrupted tokens.
I think it should be good enough., for now at least. Instead of A few more ideas for optimizations:
|
Opened microsoft/CsWinRT#1448 integrating all of these suggestions 🙂 Still waiting on confirmation from WinRT folks (or Jeremy) that there's not some magic/cursed/magic reason why we actually did specifically need that logic for the preferred token to depend on the delegate target, but hopefully that's not the case. Plus, at least we have an actual PR to leave comments on now, which should help 😄 |
Alright special shoutout to @jkoritzinsky for helping to look through all the old sources for the built-in WinRT interop and figuring out what was going on and why we had that infrastructure with those arguably weird comments. Here's what happened. The original built-in WinRT interop support was using
This comment only made sense in that scenario, where they also had APIs such as When support for WinRT was moved out of the runtime, events for RCW objects moved to a different mechanism, and So to recap, we can totally do microsoft/CsWinRT#1448 using the suggestions from Jan, and changing the logic there is completely safe now. I'm just going to close this issue then, since there's no actual API needed anymore 😄 Thanks everyone! |
Overview
We're working on improving the binary size of assemblies using CsWinRT and NativeAOT, and talking with @MichalStrehovsky we noticed that the new trimming optimization to stop delegate targets from being considered reflection-visible by default (#96166) is not kicking in at all, as it's blocked by the use of
Delegate.Method
in ourEventRegistrationTokenTable<T>
type (see here).We need to calculate a "good" pseudo-random value for a given delegate, which will be the starting value for an event registration token passed to the WinRT side. Currently we're using
Delegate.GetHashCode()
only when the delegate is multicast, because unfortunately when that is not the case, the returned hashcode only depends on the target of the delegate, and on the delegate type, but not on the specific method being wrapped by the delegate:runtime/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs
Lines 151 to 154 in 46d3b27
The way we're working around that is to check if the delegate is unicast, and in that case do
handler.Method.GetHashCode()
, which breaks that size optimization entirely. I can't really think of another way of getting some other value from the delegate without touchingMethod
at all. Additionally, Michal also mentioned that theGetHashCode()
implementation on NativeAOT could be improved.Dropping that
Method
property entirely saves over 200 KB in a minimal WinRT component sample:I don't have an exact ask here, so opening this more to try to figure out what the best approach to this could be. For instance, would it be possible to change the
Delegate.GetHashCode()
implementation for unicast delegates to also account for the method being invoked? Or, if that was a nonstarter due to the overhead it would add to all uses (even when callers would be fine with the current behavior), could perhaps a new API make sense to compute a "better" hashcode for a delegate? Strawman idea:This could compute a "better", slightly more expensive hashcode, that would combine all target methods as well.
Alternatively, could there be some other way of retrieving a "hashcode of the target method" that's trim friendly?
To also try things from another angle — @AaronRobinsonMSFT @jkoritzinsky I know you have experience in this area specifically (I mean WinRT event handling in general). Would you know whether it might be possible to actually just stop this approach entirely and not just, for instance, use a completely random value for the lower 32 bits instead? Reading all the comments in CsWinRT it's not really clear why that's not already being done. The comments say the current approach "will also give us a shot at preventing unregistration rom becoming an O(N) operation", but given the unregistration is just performed with a token that's coming from outside this class (ie. the one that callers would've stored when registering), it's not clear to me at all how this would relate to how that initial token value was calculated, and especially to whether it was a pseudo-random value depending on the exact delegate instance or not.
To clarify: the goal here is to drop that
Method
use in CsWinRT to allow the optimization in #96166 to work. I'd be happy with any approach that would allow that, be it some new API, some other equivalent workaround, or perhaps just a different logic to compute that token entirely from CsWinRT's side, assuming that didn't cause other problems or performance regressions.Also cc. @manodasanW
The text was updated successfully, but these errors were encountered: