-
Notifications
You must be signed in to change notification settings - Fork 830
Closed
Labels
Description
I'm not sure whether this might be F# imporvement or JIT.. Basically I will duplicate this in runtime repo (dotnet/runtime#58941) 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 F# compiler emit better IL here?