Skip to content

[IL Emit] Improve il code emitting when working with loops #12138

@En3Tho

Description

@En3Tho

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.

image

Can F# compiler emit better IL here?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions