-
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
[JIT] Asm difference for F# and C# methods #58941
Comments
I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label. |
@BruceForstall a loop optimization for you I guess: static void Test()
{
start:
if (Cond())
{
DoWork();
goto start;
}
} should be transformed into the same as: static void Caller2()
{
goto condition;
start:
DoWork();
condition:
if (Cond())
goto start;
} it might make code a bit bigger but loop body will be more efficient. PS: this transformation is made in Roslyn for |
Tagging subscribers to this area: @JulieLeeMSFT Issue DetailsI'm not sure whether this might be JIt imporvement or F#.. Basically I will duplicate this in fsharp repo (dotnet/fsharp#12138) too. Consider these 2 methods: [<MethodImpl(MethodImplOptions.AggressiveInlining)>]
let fold initial folder (enumerator: #IEnumerator<'i>) =
let folder = OptimizedClosures.FSharpFunc<_,_,_>.Adapt folder
let mutable enumerator = enumerator
let mutable result = initial
while enumerator.MoveNext() do
result <- folder.Invoke(result, enumerator.Current)
result [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TResult Fold<TResult, TItem, TEnumerator>(TResult result, FSharpFunc<TResult, FSharpFunc<TItem, TResult>> folder, TEnumerator enumerator)
where TEnumerator : IEnumerator<TItem>
{
var fSharpFunc = OptimizedClosures.FSharpFunc<TResult, TItem, TResult>.Adapt(folder);
var enumerator2 = enumerator;
var result2 = result;
while (enumerator2.MoveNext())
result2 = fSharpFunc.Invoke(result2, enumerator2.Current);
return result2;
} They look very similar but there is an importnat il emit difference: C# method is compiled to this basically: [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TResult FoldRoslynVersion<TResult, TItem, TEnumerator>(TResult result, FSharpFunc<TResult, FSharpFunc<TItem, TResult>> folder, TEnumerator enumerator)
where TEnumerator : IEnumerator<TItem>
{
var fSharpFunc = OptimizedClosures.FSharpFunc<TResult, TItem, TResult>.Adapt(folder);
var enumerator2 = enumerator;
var result2 = result;
goto movenext;
logic:
result2 = fSharpFunc.Invoke(result2, enumerator2.Current);
movenext:
if (!enumerator2.MoveNext())
return result2;
goto logic;
} While F# is compiled to this basically: [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TResult FoldFSharpVersion<TResult, TItem, TEnumerator>(TResult result, FSharpFunc<TResult, FSharpFunc<TItem, TResult>> folder, TEnumerator enumerator)
where TEnumerator : IEnumerator<TItem>
{
var fSharpFunc = OptimizedClosures.FSharpFunc<TResult, TItem, TResult>.Adapt(folder);
var enumerator2 = enumerator;
var result2 = result;
movenext:
if (!enumerator2.MoveNext())
goto exit;
result2 = fSharpFunc.Invoke(result2, enumerator2.Current);
goto movenext;
exit:
return result2;
} While difference might be non obvious, C# version with condition at the end of the method results in 10-15% perf imporvement while having the same assembly size. Can JIT compiler regonize these patterns better and ideally emit the same code for both variants?
|
The F# generated IL doesn't match the patterns the JIT looks for to do loop inversion or loop recognition, so the loop doesn't get added to our loop table for consideration for future optimization. We do mark the blocks with loop weights because we use a different logic for determining that. Fixing this is part of a more general task to improve RyuJIT loop recognition. |
Fixed by PRs listed in #93144 (comment) |
@jakobbotsch Thank you! |
I'm not sure whether this might be JIt imporvement or F#.. Basically I will duplicate this in fsharp repo (dotnet/fsharp#12138) too.
Consider these 2 methods:
They look very similar but there is an importnat il emit difference:
C# method is compiled to this basically:
While F# is compiled to this basically:
While difference might be non obvious, C# version with condition at the end of the method results in 10-15% perf imporvement while having the same assembly size.
Can JIT compiler regonize these patterns better and ideally emit the same code for both variants?
category:cq
theme:loop-opt
skill-level:expert
cost:large
impact:large
The text was updated successfully, but these errors were encountered: