F# VS editor: reduce background CPU load by gating expensive analyzers to the active document only
- Dominant language
- F#
- Stars
- 4.3k
- Forks
- 876
- Avg merge
- 4d 11h
- Merged PRs (30d)
- 131
Description
**Is your feature request related to a problem? Please describe.**
CPU profiling of `devenv.exe` (Visual Studio Insiders + F# extension) shows `ThreadPoolWorkQueue.Dispatch` at **72 % total / 59 % self**, driven by a continuous stream of small tasks from `FSharp.Compiler.CheckDeclarations`, `FSharpProjectOptionsReactor`, and async/`cancellableTask` state-machine overhead.
The root cause is not a single hot function but the *volume* of background tasks scheduled regardless of whether an F# document is actually active/visible. Roslyn's default `BackgroundAnalysisScope` for C# is `VisibleFilesAndOpenFilesWithPreviouslyReportedDiagnostics`, but F# analyzers currently run a full typecheck for every open tab on every crawler pass.
---
**Describe the solution you'd like**
### Already implemented in this PR
A shared `ActiveDocumentDetection` module was extracted that uses `SVsShellMonitorSelection` / `IVsMonitorSelection` / `IVsWindowFrame` to determine whether a Roslyn `Document` is the currently focused editor tab. The module is now applied to the four heaviest analyzers/services:
| Component | File | Fix applied |
|---|---|---|
| `UnusedOpensDiagnosticAnalyzer` | `Diagnostics/UnusedOpensDiagnosticAnalyzer.fs` | active-document gate (was already in place, refactored to shared module) |
| `UnusedDeclarationsAnalyzer` | `Diagnostics/UnusedDeclarationsAnalyzer.fs` | active-document gate added |
| `SimplifyNameDiagnosticAnalyzer` | `Diagnostics/SimplifyNameDiagnosticAnalyzer.fs` | active-document gate added |
| `FSharpInlayHintsService` | `Hints/FSharpInlayHintsService.fs` | active-document gate added |
The shared helper (`ActiveDocumentDetection.isActiveDocument`) falls back to `true` (= don't suppress) when the active document cannot be determined, so analysis is never silently lost.
### Remaining work (tracked in `docs/ide/background-activity-minimization-plan.md`)
| # | Source | Estimated impact | Risk |
|---|---|---|---|
| 4 | `ClassificationService.AddSemanticClassificationsAsync` — cache miss triggers full typecheck for every open tab | Medium | Low-medium |
| 5 | `createPEReference` / `Compilation.Emit` — re-emits metadata for every C# referenced project on each workspace event | High | Medium |
| 6 | `hasDependentVersionChanged` — synchronous `.Result` blocks the reactor `MailboxProcessor` thread | Medium | Low |
| 7 | `FSharpProjectOptionsReactor` — FIFO queue starves active-document requests | Medium | Medium |
| 8 | Script `updateProjectOptions` fires on every caret move | Low-medium | Low |
| 9 | `DocumentDiagnosticAnalyzer` semantic path — no text-version short-circuit | Medium | Low |
| 10 | `SymbolHelpers.findReferencedSymbolsAsync` — typechecks all documents in solution | Low (on-demand) | Low |
Full root-cause descriptions and proposed fixes are in [`docs/ide/background-activity-minimization-plan.md`](../docs/ide/background-activity-minimization-plan.md).
---
**Describe alternatives you've considered**
* Lowering `BackgroundAnalysisScope` globally via `IGlobalOptionService` — blocked by internal Roslyn API; proposed as a future Roslyn API extension in `docs/ide/api-designs/Active-Document-Background-Analysis.md`.
* Using `IDocumentTrackingService` from Roslyn — not exposed via ExternalAccess; same future-API path.
* `SemaphoreSlim` throttling alone — already in place for `SimplifyName` but does not eliminate unnecessary work, just caps it.
---
**Additional context**
* Profile was collected on Visual Studio 18.9 Insiders (`devenv.exe`) with the `/rootsuffix RoslynDev` hive.
* The immediate symptom is sluggish UI and continuous CPU consumption even when the user is not typing or switching tabs.
* The fix is fully backward-compatible: non-active documents receive fresh diagnostics/hints as soon as the user switches to them.
Contributor guide
Research direction
Start with docs/ide/background-activity-minimization-plan.md and compare it with the four analyzer/service files listed in the issue, plus the shared ActiveDocumentDetection module. Confirm which numbered items remain after the stated implementation, then verify that the selected background work is gated without suppressing diagnostics or hints when a document becomes active.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- fsharp
- Domain
- developer-experience, performance, tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100