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
RyuJIT: Guess unique interface for guarded devirtualization #37563
Conversation
I would imagine this is potentially quite expensive at jit time. This does not seem like the kind of information that should be searched for. At one point, @davidwrighton had a prototype implementation for That being said, something like this might prove useful as part of an exploration of the impact of guarded devirt on steady-state performance and code size, as a source of plausible guesses. Any thoughts on how to characterize diffs? PMI/SPMI are not useful here. |
Mmm, I ended up restricting the work that got checked in from trying to implement |
@AndyAyersMS @davidwrighton initially I wanted to add a special ILLink substep (extension) to analyze the whole app at once and store info in a file (basically a list of "unique" implementations) and read that file in JIT. If you think it's a good idea I can make a prototype 🙂 We have a similiar ILLink substep in Xamarin.iOS: https://github.com/xamarin/xamarin-macios/blob/5fb09b1b841c073b638ff66d07a779a0951dadb5/tools/linker/MonoTouch.Tuner/SealerSubStep.cs#L11-L13 (we mark all leaf implementations with "sealed" since we can't jit on iOS) |
Also, there in ILLink we can mark empty methods (in unique implementations) to focus on them as a start, e.g. foo.Dispose(); to: if (foo is FooImpl) no-op; // FooImpl has empty Dispose impl
else foo.Dispose(); optimized: if (foo is not FooImpl) foo.Dispose(); // goto BB with 0 weight (rarely executed) |
It's tricky for RyuJit to initiate any sort of type discovery, so you'd need to read in those linker produced data files on the runtime side and decide how to robustly identify types via text so the runtime can map them to the right data structures and then relay that data to the jit. Since we're just experimenting and you're already touching the assemblies in the linker and we're not worried that much yet about jit-time perf, seems like it might be simpler to convey this information via custom attributes. We've done a bit of brainstorming internally to try and address a similar problem: how to convey analysis data from AOT to JIT, in the context of crossgen2. No concrete proposals as of yet. Another idea along these lines is to do it all in process, say by leveraging the TieredPGO stuff I added in #36887 and do some sort of method table profiling, and make that data available in real time back to Tier1. I have a bit of experience with probabilistic online algorithms that try and determine the most frequently occurring element in a stream using a fixed amount space, so it might be interesting to experiment with something like that (or similarly, try and tap into the VSD cache and see what sort of cell is in place). Ultimately even when we have a decent source of predictions we may not want to act on all of them. We may need profile data to or may need to wait for deopt to be able to really take advantage. Hard to say for sure without data. |
@AndyAyersMS I've changed the implementation to rely on custom attributes, e.g.: [UniqueImpl(typeof(Spider))]
public interface IAnimal
{
int GetLegsCount();
} and rely on ILLink to define and use it where needed. it's not a perfect solution obviously but it's a way better than iterating all types each call. I understand that there should be a more complicated solution under the hood, ideally with the ability to de-optimize code if the assumption that the impl is unique becomes invalid for a method. Anyway will try to come up with some ILLink task + find related benchmarks. |
(Accidentally hit close...) Would it make sense to also allow a bool flag on the an attribute that says: this is the only implementation, and I know the app doesn't use ref emit and Assembly.Load, hence you don't need to guard? |
Sounds like a good idea |
A good use-case for the Guarded Devirtualization is a quite popular case when an interface has only one implementation, I noticed that this case wasn't finished (getUniqueImplementingClass(objClass) was commented) and decided to try to add a super-naive implementation for
getUniqueImplementingClass
in VM.Example:
Current codegen for
CallInterfaceMethod
^ nothing is devirtualized, just a regular virtual call.
Codegen with
COMPlus_JitEnableGuardedDevirtualization=1
and this PR:So guarded devirtualization transformed:
to:
Now let's add one more
IAnimal
implementation and check codegen again:Interface has two implementations - the optimization gives up as expected.
cc @AndyAyersMS
PS: this code is Debug/Checked configuration only.