Skip to content

Compilation gets much slower when using many computation expressions #14429

@alfonsogarciacaro

Description

@alfonsogarciacaro

In a big Fable project, after a big refactoring to introduce a convenience computation expression, the project started to take much longer to compile. The overhead came from the type checking phase of FSharp.Compiler.Service (it also takes a long time when running dotnet build). The computation expression is a convenience to generate React fragments out of React elements. The CE accepts single React Elements as well as sequences and optional elements. The code is like this:

type ElementBuilder() =
    member _.Zero () : ReactElements =
        []

    member _.Yield (e: ReactElement) : ReactElements =
        [e]

    member _.Yield (maybeE: Option<ReactElement>) : ReactElements =
        match maybeE with
        | None   -> []
        | Some e -> [e]

    member _.Yield (maybeEs: Option<List<ReactElement>>) : ReactElements =
        match maybeEs with
        | None    -> []
        | Some es -> es

    member _.Yield (es: List<ReactElement>) : ReactElements =
        es

    member _.Yield (es: seq<ReactElement>) : ReactElements =
        Seq.toList es

    member _.Yield (ess: list<list<ReactElement>>) : ReactElements =
        List.flatten ess

    member _.Yield (ess: seq<list<ReactElement>>) : ReactElements =
        ess |> Seq.collect id |> Seq.toList

    member _.Combine (e: ReactElement, es: ReactElements) : ReactElements =
        e :: es

    member _.Combine (moreEs: List<ReactElement>, es: ReactElements) : ReactElements =
        moreEs @ es

    member _.Combine (moreEs: seq<ReactElement>, es: ReactElements) : ReactElements =
        (Seq.toList moreEs) @ es

    member _.Combine (moreEs: List<List<ReactElement>>, f: unit -> ReactElements) : ReactElements =
        (List.flatten moreEs) @ (f())

    member _.Combine (e: ReactElement, f: unit -> ReactElements) : ReactElements =
        e :: (f())

    member _.Combine (moreEs: List<ReactElement>, f: unit -> ReactElements) : ReactElements =
        moreEs @ (f())

    member _.Combine (moreEs: seq<ReactElement>, f: unit -> ReactElements) : ReactElements =
        (Seq.toList moreEs) @ (f())

    member _.Combine (moreEs: List<List<ReactElement>>, es: ReactElements) : ReactElements =
        (List.flatten moreEs) @ es

    member _.Delay (expr: unit -> ReactElements) : unit -> ReactElements = expr

    member _.Run (f: unit -> ReactElements) : ReactElement =
        let elements = f()
        Fable.React.Helpers.fragment [] elements

let element = ElementBuilder()

There's a similar CE ElementsBuilder that works the same but returns ReactElement list instead of the fragment.

Users tried changing the CE in different ways, but the only thing that worked was to remove the CE in most of the files. It is still used in some of them but compilation times are mostly back to normal.

The most interesting part is I tried profiling this, and the result is most of the time is spent in the function FSharp.Compiler.NameResolution.ResolveLongIdentAsModuleOrNamespaceThen. This makes me wonder if the CE is not responsible for the slow compilation but instead it causes a problematic name resolution directly. Both ElementBuilder and element are in the same module decorated with AutoOpen.

image

Any hints to improve the performance of the compilation?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area-ComputationExpressionsEnd-to-end experience for computation expressions (except async and state machine compilation)Theme-Performance

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions