Repository navigation
Compile with DOTNET_TieredPGO=0 DOTNET_TC_CallCountingDelayMs=0 for faster compilation #20802
Description
Is your feature request related to a problem? Please describe.
Compilation is slow.
Describe the solution you'd like
Setting DOTNET_TieredPGO=0 DOTNET_TC_CallCountingDelayMs=0 for the compiler process (fsc.dll) can significantly speed it up.
Describe alternatives you've considered
Additional context
At G-Research I have spent considerable time trying to optimise F# compilation times of our large solution (mixed C# + F#).
As part of that I discovered that the following two runtime JIT flags have a large impact on compilation times & CPU time:
DOTNET_TieredPGO=0DOTNET_TC_CallCountingDelayMs=0
Internal tests at G-Research against internal projects showed large speed ups.
We have onboarded these settings in some of our environments.
Today I executed extensive AI-driven multi-hour tests at home against the compiler codebase and saw similar results.
Reports
A few AI-generated reports with varying level of detail:
- Markdown report: https://github.com/safesparrow/fsharp_misc/blob/main/reports/pr/report.md
- HTML version: https://safesparrow.github.io/fsharp_misc/reports/pr/report.html
- Extended HTML report - all other configurations tested, more details and interactive charts: https://safesparrow.github.io/fsharp_misc/reports/report.html
Results summary - AI-generated
- All 14 compilations of
FSharp.Compiler.Service.slnxtogether (newest target framework of each project, parallel compilation on): 63.1 s → 36.3 s wall (−42.5%, 95% CI −43.4% to −41.9%), CPU −31.0% (−32.7% to −29.4%). - Medium and large compilations (6 of 14, 1.5 s to 25 s): wall −56% to −36%, CPU −43% to +2%. FSharp.Compiler.Service itself: 24.4 s → 13.5 s.
- Small compilations (8, under 1.5 s): wall −19% to −3%, but CPU +20% to +49%: the process ends before recompiling startup code pays off.
- Whole
dotnet buildof Fantomas (SDK 11, 6 runs per mode): −18.7% wall with the settings on fsc only - GC pause time is unchanged (−1.1%). JIT CPU time goes up (+202%), mostly on the background tier-1 thread, which is what buys the speed-up.
This vs a compilation server
Note that this is related to the F# compilation server concept that we have been working on.
For server-based compilation, the default runtime settings are adequate and changing them is likely to reduce performance - but server compilation is not yet available, and this could be a nice easy speed up until it is.
Results
The two sections below describe compiling the full compiler solution with 14 projects.
Speed up per project
All large & medium projects show significant wall-clock time and CPU time reduction.
The smallest projects (that compile in ~1s) show slightly reduced wall-clock time and increased CPU time.
Aggregate timings of each compilation phase
| All compilations, sum of medians | Defaults | Flags | Δ (95% CI) |
|---|---|---|---|
| Wall time | 63.1 s | 36.3 s | -42.5% (-43.4 to -41.9) |
| CPU time | 210.0 s | 144.8 s | -31.0% (-32.7 to -29.4) |
| JIT time (all threads) | 9.1 s | 27.6 s | +201.9% (+190.1 to +206.2) |
| GC pause time | 6.1 s | 6.1 s | -1.1% (-2.0 to +0.6) |
| Phase: Import references | 2.9 s | 2.3 s | -19.3% (-20.2 to -18.4) |
| Phase: Parse | 2.2 s | 2.0 s | -7.2% (-8.6 to -5.2) |
| Phase: Typecheck | 23.6 s | 12.2 s | -48.2% (-51.6 to -47.5) |
| Phase: Optimizations | 18.1 s | 9.5 s | -47.3% (-48.2 to -45.4) |
| Phase: TAST -> IL | 5.8 s | 3.6 s | -39.3% (-41.2 to -34.9) |
| Phase: Write .NET binary | 6.1 s | 3.6 s | -41.9% (-42.7 to -39.5) |
| Phase: Other phases | 2.4 s | 1.4 s | -44.3% (-47.0 to -42.2) |
| Phase: Startup & exit | 1.5 s | 1.5 s | +0.1% (-0.8 to +1.0) |
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNew
Activity
safesparrow commentedon Oct 17, 2026
@T-Gro I forgot to discuss these in more detail earlier this week. These are settings that in my testing significantly speed up compilation across all codebases tested, and are a middle ground between the current setup and a future F# compilation server.
As these are completely safe in terms of compilation outputs (determinism etc), it should be easier to get these in.
I will raise an issue for the compilation server separately.
baronfel commentedon Oct 17, 2026
@safesparrow when you say that applying these flags to MSBuild also resulted in wall clock improvements, was that with or without MSBuild Multithreaded Mode (-mt) in use? I'm very curious about mor general build performance improvements too, but with the MSBuild Server (which MT uses) I would expect the tiered compilation to benefit us over time.
safesparrow commentedon Oct 17, 2026
@baronfel Sorry, that was without node reuse, so without the server either - better stability run-to-run, but not actually relevant for judging MSBuild changes.
Please discard that point about MSBuild speed ups. As with the compiler server, long-running processes like MSBuild worker nodes/server should work best with the default settings.
T-Gro commentedon Oct 21, 2026
I see the JIT time went up -> I will test if just disabling QuickJit will bring similar result.
So that precompiled code could still participate when its available (e.g. the BCL).
safesparrow commentedon Oct 22, 2026
Method count per JIT type, from https://safesparrow.github.io/fsharp_misc/reports/report.html#gridSection :
T-Gro commentedon Oct 23, 2026
I ran a few experiments comparing the switches with QuickJit disablement too, and checked the impact of MIBC data.
(FsToolkit, Oxpecker, Nu, FsAutoComplete, FSharp.Core, FSharp.Compiler.Service)
On top of what the SDK ships today (R2R), the switches give -25% to -55% wall clock time.
The "worst" possible outcome is +21% CPU (small projects) while still delivering -25% reduction of wall clock time - so still worth it. The CPU degradation turns into a CPU saving for bigger projects (up to -46% for FCS).
Peak memory usage also has a slight increase (up to +6%) for small projects, and is neutral or slightly better the bigger the project is (FCS -5%) - so in my view, also worth taking in.
Disabling QuickJit alone is not an alternative - anywhere between -35% and +22% wall clock. Your switches are better on all 6 projects.
MIBC: we pin the profile packages, but they never reach crossgen2 today - the SDK compiles
FSharp/without--mibc. I tried passing it. It adds ~500 precompiled methods (mostly FSharp.Core generics), but no measurable effect - neither alone (-6% to +1%) nor together with your switches (-5% to +2%). Your switches alone do the job, MIBC's impact is not that big.T-Gro commentedon Oct 23, 2026
TLDR: Let's do it.