-
Notifications
You must be signed in to change notification settings - Fork 789
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
Boolean operations generated IL #635
Comments
The il for the F# version:
While the C# version is:
|
Ok forget about That being said, the IL generated by F# is longer and slower than the one generated by C#... Comparing the IL..
so instead on conditionally branching, it pushes the result on the stack, then compare it to 0 before branching to IL_001b where there is a conditional branch on false:
This looks quite convoluted compared to C# straight forward implementation. Any reason for this ? |
Ok I've seen where it comes from..
And there seem to be no further optimization. |
I've seen that the Maybe it is possible to combine Matches together in an optimization pass to remove redundant code..
Or something close to it... |
This is indeed interesting. @thinkbeforecoding you probably already seen this https://github.com/Microsoft/visualfsharp/blob/master/src/fsharp/Optimizer.fs#L2848 wondering if you have thoughts on it. The I'm taking a wild guess, I wish I knew what to do, but I don't. Maybe something can be done here for |
True, the --debug- removes the |
If it can be of any help, here's commented optimized assembly from RyuJIT for C# and F# compared:
So the JIT is closely following the IL, can't rely on it to optimize this case. |
@asik - could you add instructions to DEVGUIDE.md about how to get that generated native code for a method? That's very useful! |
I've been taking a glance through issues and noticed this one, where we can definitely improve codegen here though finding the general rule needs some work. I'll mark it as a bug to aid with tracking even though it's really a perf improvement. I think this is going to best boil down to an Optimizer.fs rewrite that merges the switching/choice logic of one decision tree into another in some situations. The general situation seems to be close to this (these are rough notes). First use notation
for TAST decision trees made up only of TDSwitch and TDSuccess nodes and with no values bound in the transfer to the targets (that is the expression list is empty in the TDSuccess nodes). Assume for now that the TDSwitch nodes are always switching over boolean values (that is, each TCase has a Test which is a Const boolean). So the natural form of things like
The aim seems to be to rewrite this to
Note that this duplicates the decision trees implied by Choices2, but decision trees are small in any case. And for Slightly more generally we have
which will be rewritten to
The tricky thing is to determine the exact situation in which this rule applies and to formulate this as code. (aside: is this more useful than just boolean switches? Is it useful for other inputs like option or enumeration or integer matching, e.g. |
Nice to see some move on this :) |
I've been digging a bit more to understand where optimizations are done... I traced the compiler optimization on the following exemple:
The generated IL is:
as expected.
Where it could probably be: |
So I made a draft implementation of this...
is now compiled like:
There is no comparison to constants anymore. The problem is that the right part (c >='A' && c <= 'Z') is duplicated in the output... |
Can you share a link to your branch? Thanks |
Here is the branch: And here is the sample I use for tests: it builds the fs sample using the fsc version under deployment and usual fsc version as well as a C# version of the same code. |
I was doing perf test today and noticed that F# didn't use intrinsic boolean operator with && and ||.
C# also makes some optimizations in release, but the F# version is a bit slower that C#.
The test case uses the following function:
when decompiled with dotPeek it looks like:
The following equivalent C# implementation which is highly similar:
Results in release in:
If the last lines look the same, F# uses a ternary construct with 1s and 0s instead of using && and || ...
A micro benchmark on 100.000.000 iterations doing the full path (always testing a '}') gives
~417ms for the F# version
~280ms for the C# version
Tests with other charts give an advantage for C#.
The question is: is there a good reason to use ?: == 0 == 1 instead of Boolean && and || native operators ?
The text was updated successfully, but these errors were encountered: