Skip to content

F# - ValueOption suspiciously slow compared to Option and Result #83972

@kerams

Description

@kerams

Runnable benchmark https://github.com/kerams/oh-dear.

The test is very simple - we do a single remapping of ValueSome, Some and Ok. (MethodImpl probably has no effect here.)

[<MemoryDiagnoser>]
type Current () =

    [<MethodImpl(MethodImplOptions.NoInlining)>]
    let o = Some 10

    [<MethodImpl(MethodImplOptions.NoInlining)>]
    let vo = ValueSome 10

    [<MethodImpl(MethodImplOptions.NoInlining)>]
    let r = Result<int, string>.Ok 10

    [<Benchmark>]
    member _.Option() =
        match o with
        | Some x -> Some (x + 3)

    [<Benchmark>]
    member _.Voption() =
        match vo with
        | ValueSome x -> ValueSome (x + 3)

    [<Benchmark>]
    member _.Result() =
        match r with
        | Ok x -> Result<int, string>.Ok (x + 3)

Decompiled IL looks as expected.

[Benchmark(19, "C:\\code\\Test\\Bench\\Program.fs")]
public FSharpOption<int> Option()
{
    FSharpOption<int> fsharpOption = this.o;
    if (fsharpOption != null)
    {
        FSharpOption<int> fsharpOption2 = fsharpOption;
        int x = fsharpOption2.Value;
        return FSharpOption<int>.Some(x + 3);
    }
    throw new MatchFailureException("C:\\code\\Test\\Bench\\Program.fs", 21, 14);
}

[Benchmark(24, "C:\\code\\Test\\Bench\\Program.fs")]
public FSharpValueOption<int> Voption()
{
    FSharpValueOption<int> fsharpValueOption = this.vo;
    if (fsharpValueOption.Tag == 1)
    {
        int x = fsharpValueOption.Item;
        return FSharpValueOption<int>.NewValueSome(x + 3);
    }
    throw new MatchFailureException("C:\\code\\Test\\Bench\\Program.fs", 26, 14);
}

[Benchmark(29, "C:\\code\\Test\\Bench\\Program.fs")]
public FSharpResult<int, string> Result()
{
    FSharpResult<int, string> fsharpResult = this.r;
    if (fsharpResult.Tag == 0)
    {
        int x = fsharpResult.ResultValue;
        return FSharpResult<int, string>.NewOk(x + 3);
    }
    throw new MatchFailureException("C:\\code\\Test\\Bench\\Program.fs", 31, 14);
}

Jitted on .NET 7 on Sharplab.

Benchmark results look like this, reproducible across multiple runtimes and CPUs (all results here).

BenchmarkDotNet=v0.13.5, OS=Windows 11 (10.0.22621.1413/22H2/2022Update/SunValley2)
AMD Ryzen 9 7900, 1 CPU, 24 logical and 12 physical cores
.NET SDK=8.0.100-preview.2.23157.25
 [Host]     : .NET 8.0.0 (8.0.23.12803), X64 RyuJIT AVX2 DEBUG
 DefaultJob : .NET 8.0.0 (8.0.23.12803), X64 RyuJIT AVX2

Method Mean Error StdDev Gen0 Allocated
Option 2.0639 ns 0.0204 ns 0.0171 ns 0.0014 24 B
Voption 3.0861 ns 0.0381 ns 0.0337 ns - -
Result 0.2330 ns 0.0071 ns 0.0063 ns - -

ValueOption remapping is slower than Option despite the fact that Option needs to allocate. I'd expect performance to be at least as good as in the Result case (since the struct size of ValueOption is smaller than Result).

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMItenet-performancePerformance related issue

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions