-
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
Internal API suggestion: bool JitHelpers.IsKnownConstant(uint) to let us optimize certain code paths #11484
Comments
Thanks for opening this issue. This is a really interesting idea, but I have some concerns about its usefulness under the current JIT design. First of all, when should JIT replace the call-site of
|
I find this to be a very clever idea! I have never before seen the idea of communicating information from the compiler to the code. Normally, the code communicates assumptions to the compiler. |
I think most of what you want here exists already in the jit, though the parts don't always work as well as they could. The importer does aggressive branch pruning and the inliner does forward substitution of constant arguments, so for the most part available constants will show up early, and if not, should show up later on during the optimization phases. The one bit that is not as powerful as perhaps you'd like is the modelling of the impact of constant args on larger method bodies in the inline heuristics. There is some support, but the recognition is simplistic and the benefit model is pretty crude. I have experimented in the past with more thoughtful heuristics but was held back in part by the desire not to slow down the jit. With the advent of tiering one could perhaps argue that making the Tier1 jit a bit slower is acceptable. Alternatively, we could look at offloading parameter impact analysis to an AOT tool. Various folks have been kicking around these ideas recently (cc @morganbr @sergiy-k). This would hint to the jit that aggressive inlining based on constant args would pay off; it would still be up to the jit to actually inline and simplify. So I'm curious: what happens today when you use coding patterns like the above? Maybe we can collect examples here so they're all in one place and get a better picture of where things fall apart. Addressing the related issues in the jit would provide a wider benefit than introducing a new specialized helper. |
cc @dotnet/jit-contrib |
I agree that addressing issues JIT-wide would probably have the greatest overall impact, since it could benefit almost all code that follows the conventions you decide are optimization candidates. The switch statement scenario is a prime candidate - developers shouldn't have to write extra code "please optimize me!" if the JIT can make things a bit nicer on their behalf automatically. The scenarios where I think this doesn't quite work are scenarios where the JIT would need special knowledge of how a type behaves in order to provide the maximum possible optimizations. The |
Another case where this would be useful is in the following code: public T GetElement(int index)
{
ThrowIfUnsupportedType();
if ((uint)(index) >= (uint)(Count))
{
throw new ArgumentOutOfRangeException(nameof(index));
}
ref T e0 = ref Unsafe.As<Vector128<T>, T>(ref Unsafe.AsRef(in this));
return Unsafe.Add(ref e0, index);
} If the index is a constant value, it is much more efficient for us to emit a Edit: This can, of course, be handled as an intrinsic directly in the runtime (and is probably what will get done), but an |
Still on the fence about the value of this -- labelling as "future" |
Per Tanner's comment, I wonder if adding a bunch of |
Is there a reason we wouldn't want to propose this or a variant of it as being public? And are there other places we should ourselves be using it internally now that we have it internally? |
I personally don't have a strong opinion. If we decide to make it public we will need to address that inline-limit issue and properly implement it in Mono too. |
This API can be used to create behavioral differences depending on what the call site looks like, what surrounding code looks like, inlining decisions, tiering, the runtime version, bitness, the phase of the moon, ... It's also very hard to test this for correctness. You have to hope that the JIT does what you expect it to do so that the test is valid. This seems like a very dangerous thing to expose publicly. In my opinion, clearly not worth it. I don't want to see this show up in libraries causing nasty bugs. At least, the decision should wait for a release or two to gain practical experience with this concept. @stephentoub |
This is no more dangerous than many of the APIs in Users can already do all sorts of hacks, specializations, and other tricks (including relying on inlining) to force a particularly behavior and this just extends that to one other interesting avenue that we ourselves find worthwhile in various scenarios. It's not like every app would suddenly start using it and it being exposed somewhere like |
There are quite a few places in the Framework where we have specialized knowledge of data structures and algorithms - beyond what the JIT has - where we'd be able to make optimizations beyond what the JIT can provide if the JIT is able to hint to us information about locals.
For example, consider
string.StartsWith(char) : bool
, which is currently implemented as below.If the JIT were able to hint to us information about the local, we could write this as follows, which would allow us to elide the first branch entirely in almost all cases, taking advantage of the fact that empty strings have the null-terminator in the
_firstChar
position.It would also allow us to further optimize the
Span.Slice(int start, int length)
logic we checked in earlier, which allows us to do something akin to the below.This could also have potential applications for methods which are implemented via a giant switch statement which normally doesn't get inlined (see
String.Equals(..., StringComparison)
overload). We could choose to implement the method as an inlineable method that does a very fast dispatch if the comparison parameter is known constant, falling back to a slower non-inlined method if the comparison parameter needs to be evaluated at runtime.The behavior of
IsKnownConstant
would be such that the JIT would replace it with true if the value is known at JIT time to be constant (perhaps because it is itself a literal or has been calculated from some other const-folding). It would be replaced with false in all other cases, including if the JIT simply doesn't have enough information to make a determination, or if optimizations have been disabled. I'm also assuming branch pruning would kick in to completely elide code that is on the incorrect side of theIsKnownConstant
check.category:proposal
theme:optimization
skill-level:expert
cost:extra-large
The text was updated successfully, but these errors were encountered: