dotnet / dotnet/fsharp

Compilation gets much slower when using many computation expressions

Open
#14,429 23 comments 5 reactions 0 assignees View on GitHub
Area-ComputationExpressions Theme-Performance
Dominant language
F#
Stars
4.3k
Forks
876
Avg merge
4d 11h
Merged PRs (30d)
131

Description

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:

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

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

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

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

member _.Yield (es: List) : ReactElements =
es

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

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

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

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

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

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

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

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

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

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

member _.Combine (moreEs: List>, 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](https://user-images.githubusercontent.com/8275461/205439265-a5a9eda1-b884-4652-a9f3-fdea0f0d451b.png)

Any hints to improve the performance of the compilation?

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.