-
Notifications
You must be signed in to change notification settings - Fork 802
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
Performance: Not possible to prevent inlining the IL by the F# compiler without the MethodImplOptions.NoInlining flags #5178
Comments
@zpodlovics This is a language suggestion, best to put it at http://github.com/fsharp/fslang-suggestions |
@zpodlovics Am I correct that the workaround is to use |
@dsyme Thanks, I will move it to F# suggestion repo. Unfortunately the noinlining marker is not a workaround, because of this comment (#5019 (comment)): "The throw helper does all formatting and exception object creation, then unconditionally throws. It is deliberately not marked with [MethodImpl(MethodImplOptions.NoInlining)]." |
@zpodlovics I'm confused.
But you additionally want
Could you briefly explain why? |
@dsyme Right now the C# compiler will not inline the static methods IL automatically (at least not in the examples I tried), so it possible to create method with split-path execution. This is the model of the execution and optimalization (hopefully not far from the current .NET Core implementation): Let assume I have a busy webserver with an existing standard API with millions of requests per second and I have to do some simple calculation C1 on an array (100 IL instructions). I also have some domain knownledge: for this usage the array one in million execution will be empty and require a bit more complex logic to handle this case. If I split the execution path in two part: one for the non-empty case (25 IL instruction) and one for the empty case (75 IL instruction), than the overall performance could be significantly higher: C1 Original: 100 IL instruction * 1 million execution However sometimes I have an another busy functionality in the other part of the software that use the same calculation but in this execution path the most common path is the empty case. The NoInlining marker in this case will prevent the JIT to inline to this execution path. Right now with F# I have the following cases:
Using the not yet implemented .NET Core JIT is now tiered, and could make decisions based on execution profile and relatively safe to assume that this profile based optimalization path will be explored more deeply in the future: I know it's a hard decision to introduce another concepts in the language (but at least it will be symmetric: inline + noinline) and make it more complex. So I am open for suggestion if there is an existing way or a simpler solution to express this split path execution in F# (other than write it in C#). Unfortunately even a few additional instructions matters a lot when it executed million times+ a second. |
OK, thanks, that's helpful context. So to be clear: you want something which tells F# not to inline, but allows the JIT to inline if it wants? If so that's a reasonable request. But just to mention we couldn't call it |
@dsyme Yes, exactly. I would like to tell the F# compiler not to inline, but allows the JIT to inline if it wants. You are right, naming thing correctly is critical. I would be happy to use any constructs (attribute, keyword, whatever) to control this.: an F# specific attribute would be a perfect choice. |
Isn't that the default? Isn't the jit always free to inline until told otherwise?
|
@zpodlovics I'll close this out since this is a language suggestion and should be tracked at http://github.com/fsharp/fslang-suggestions as previously mentioned. |
If anyone interested there is a developer flag to change the default inlining behaviour, however it's hard to use correctly and the developer flag will be applied to the whole project:
A more fine grained solution will be needed to control the compiler inlining behaviour. |
I am trying to do some performance optimalizations using the ThrowHelper like construct as of #5019 (comment) :
The main method does its checks (say argument validation) and then conditionally calls throw helper with any needed args. Type the helper so no implicit boxing or parms array allocation is needed.
The throw helper does all formatting and exception object creation, then unconditionally throws. It is deliberately not marked with [MethodImpl(MethodImplOptions.NoInlining)].
However it seems the F# compiler still inlines all the small functions IL without the MethodImplOptions.NoInlining flag, and also inlines the small static method calls IL without this attribute. It should be possible express non IL inlining for F# functions and static and non-static methods without marking it MethodImplOptions.NoInlining.
Please provide a succinct description of the issue.
Repro steps
Provide the steps required to reproduce the problem
The compiled IL code will looks like this:
Expected behavior
Should be possible to express non IL inlining small functions and static and non static method without marking it with MethodImplOptions.NoInlining.
Actual behavior
F# compiler IL inlining small functions and static and non static method without marking it with MethodImplOptions.NoInlining.
Possible/Known workarounds
Implement the helper in C# (not yet tested)
The best future solution probably would be using the
noinline
keyword asinline
already used for functions and static methods/methods (but this will complicate the language a little bit more). However it would be a perfecly fine future solution to have a[<NoInline>]
flag in F# for functions/static methods/methods.Related information
The text was updated successfully, but these errors were encountered: