-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Optimizer hints - Unsafe.Assume #4966
Comments
cc @CarolEidt |
It would be great to design such a feature to capture both the assertion about the expected behavior as well as allowing that behavior to be validated. That is, perhaps in Debug one might check the condition, while when generating optimized code (e.g. in a Release build), one might assume it to be true. We've already got Debug.Assert and Trace.Assert. Maybe this should be Unsafe.Assert, and it would check the condition in Debug builds, but assume the condition for optimization purposes in the Release build. |
Sounds interesting. How would this be expressed in IL? |
Just like any other method call. It's just that the JIT assumes that the expression passed as argument always evaluates to true and can attempt to infer useful information from that. That said, the "infer" part is a bit of a can of worms. Any |
This can be tremendously useful because it's a way turn off any JIT safety. This design is very extensible because with one API addition more and more assumptions can be made to work in the future. There should be a small catalog of patterns that are recognized. (Update: Removed the builtin_expect thought and posted them elsewhere.) |
@GSPP elsewhere would then be https://github.com/dotnet/coreclr/issues/6024 just so we have the link :) |
Sounds like you want the exising Contract.Assume but without the tooling. https://msdn.microsoft.com/en-us/library/system.diagnostics.contracts.contract.assume(v=vs.110).aspx |
@Daniel-Svensson Using Another interesting use of public int[] Sum ( int[] op1, int[] op2 )
{
var result = new int[op1.Lenght];
Contract.Assume( Performance.NonTemporal(result) );
/// here goes result[i] = op1[i] + op2[i]
} And that can be done to achieve very interesting results public int[] Sum ( int[] op1, int[] op2 )
{
if ( op1.Length > 4096 ) // or whatever it makes sense for the kernel
{
Contract.Assume( Performance.NonTemporal(op1) );
Contract.Assume( Performance.NonTemporal(op2) );
}
var result = new int[op1.Lenght];
Contract.Assume( Performance.NonTemporal(result) );
/// here goes result[i] = op1[i] + op2[i]
} This is pretty handy optimization because by the time the kernel ends the cache is poluted with only the last batch of data. |
As an extension to this, it might be useful to specify contracts on return values from methods. A strawman: [return: AssumeNotNull]
public static object MyMethod();
[return: AssumeNonNegative]
public static int MyOtherMethod(); For example, the expression |
@GrabYourPitchforks The caller can already specify anything like this:
The advantage of this would be that a single unified system (the assume method call) suffices for anything. This would be per callsite customizable as well. On the other hand, those attributes would help all callsites and can be library provided. |
A related idea would be to automatically analyze methods and discover invariants such as non-null return value. This seems very expensive to do in the JIT. It could be implemented as a tool that can optionally be run on any assembly or application. The tool would produce a file that records all discovered invariants and data flow information. The JIT would consume that data. This could discover all kinds of useful information such as non-nullness, more precise types, integer ranges and constants. |
You don't need an external file, you can bake that into the assembly itself when building. |
I had provided a similar proposal (these used to be linked, but I guess that was dropped in the migration): #24593 I believe the main difference is that this one is a general While in my proposal, I suggested explicit methods (such as |
Just a quick draft for dotnet/runtime#4966 for both RyuJIT CoreCLR and Mono-LLVM (maybe it will help to push it forward since it's 4 years old). It allows programmers to give hints to JIT about branches' probabilities, e.g. to avoid goto hacks [like this](https://github.com/dotnet/runtime/blob/469ece74b6e5b7e94991dde21b3c2409194ceab9/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/CastHelpers.cs#L523) in perf critical code. #### Sample: ```csharp static int Foo(int a) { if (Unsafe.Unlikely(a <= 0)) // hint: `a` is unlikely to be <= 0 return a * a; return 0; } ``` #### Codegen without `Unlikely`: ```asm 85D2 test edx, edx 7F06 jg SHORT G_M36845_IG05 8BC2 mov eax, edx 0FAFC2 imul eax, edx C3 ret ;; return a * a G_M36845_IG05: 8BC2 mov eax, edx C3 ret ;; return 0 ``` #### Codgen with `Unlikely` (this is what this PR emits) ```asm 85D2 test edx, edx 7E03 jle SHORT G_M4270_IG05 8BC2 mov eax, edx C3 ret ;; return 0 G_M4270_IG05: 8BC2 mov eax, edx 0FAFC2 imul eax, edx C3 ret ;; return a * a ``` Also, I implemented it for Mono-LLVM (I use `@llvm.expect.i32` intrinsic, which is emitted when you use `__builtin_expect` in C/C++) #### Mono-LLVM without `Unlikely` ```asm mov ecx, edi imul ecx, ecx ;; Mono-LLVM emits branchless code for our sample xor eax, eax test edi, edi cmovle eax, ecx ret ``` #### Mono-LLVM with `Unlikely` ```asm xor eax, eax test edi, edi jle 1 <gram_Foo1__int_+0x7> ;; branch is better than cmove in this case ret ;; return 0 imul edi, edi mov eax, edi ret ;; return a * a ``` cc @jkotas
The common suggestion is: Unsafe.Assume((p & 0x1F) == 0); However, wouldn't it make more sense and be more maintainable in our current architecture that we use methods similar to test libraries: namespace System.Runtime.CompilerServices {
public static unsafe partial class Unsafe
{
[Intrinsic, NonVersionable]
public static void AssumeEqual<T>(T first, T second);
[Intrinsic, NonVersionable]
public static void AssumeNotEqual<T>(T first, T second);
[Intrinsic, NonVersionable]
public static void AssumeNull<T>(T? val);
[Intrinsic, NonVersionable]
public static void AssumeNotNull<T>(T? val);
[Intrinsic, NonVersionable]
public static void AssumeIsType<T>(object? obj);
[Intrinsic, NonVersionable]
public static void AssumeIsAssignableFrom<T>(object? obj);
}
} I find the variant that uses a bool as parameter ( Alternatively, I would simply find a new C# syntax that is introduced useful. public unsafe int MyUnsafeMethod(int a, int b) {
assume a == 1;
// or:
__assume a == 1;
return a + b;
} |
I think that compiler intrinsic is much better option. I would also assume that having specific named methods make it easier to figure out specific scenarios. It also avoids the "any boolean expression goes here, only a few are actually recognized". The downside is that things like:
May create a lot of methods (then again, this is self documenting for what is actually supported). |
C/C++ compilers allows passing hints to the optimizer using
__assume
or similar constructs. It would be useful to have same capability in .NET.One use case was discussed in https://github.com/dotnet/corefx/issues/5474#issuecomment-172653606:
category:proposal
theme:optimization
skill-level:expert
cost:large
The text was updated successfully, but these errors were encountered: