Repository navigation
Profiler: a CPU sample in hidden F# match code keeps its method's file - #19
Merged
Merged
Conversation
MelbourneDeveloper
added a commit
to Nimblesite/SharpLsp
that referenced
this pull request
Oct 20, 2026
…op is sampled where it runs (#409) ## TLDR Fixes two defects that made the F# profiler suites flaky (#408). An F# CPU sample in a `match`'s decision tree now keeps its method's file, and FsOrderLedger's burn loop is now sampled where it actually runs. ## Details - **`lspkit` e6e4172 → 3cd5896 (Nimblesite/lspkit#19, fixes lspkit#18):** - The .NET engine's `Symbolizer` gives a frame on a hidden sequence point its method's nearest visible sequence point, or the method's first, read from the PDB through `SourceLocator`. A method with no visible point, such as a compiler-generated closure, stays unlocated. - Before this, such frames lost their file, so a function split into a located hot function and an unlocated twin. That was 61% of FsPricingEngine's `Csv.parseLine`, and it failed the Windows profiler chunk on #406 (run 39364686929). - lspkit's spec ([PROF-DOTNET-CPU-SYMBOLS]) and plan state the rule. - Its fixture `HotLeakAppFs` gains `Matching.fs` and a `classify` command. The Program.fs lines SharpLsp's `profiler-fsharp-e2e` pins (up to 97) are unchanged. - **`FsOrderLedger` `Gate.fs` and `Program.fs`:** the burn tick no longer takes the process-wide gate. - It only reads (`priceAll` snapshots under the ledger's own lock). - EventPipe's sampler stops a thread at its next safe point, so the uncontended lock call took 98% of the burn thread's samples. dotnet-counters showed 0 lock contentions per second. - That left `priceAll` at the 2% hot-function threshold, and "the burn loop is hot" failed two runs in three on macOS. - Line counts are unchanged, so `PRICE_ALL` (Ledger.fs 63–70) still holds. - **`docs/plans/PROFILER-PLAN.md`:** both defects join the F# real-program list. ## How Do The Automated Tests Prove It Works? - **lspkit's `cpu_samples_in_fsharp_match_code_keep_their_file`:** before the fix, `Matching.classify` came back twice, with 948 samples located and 179 unlocated. After the fix it passes unchanged. lspkit CI is green on the PR (run 39371497743) and on main (run 39371510065). - **FsPricingEngine through this branch's published engine (`run parse`):** `Csv.parseLine` went from 1592 located and 2507 unlocated samples to 3271 located and none unlocated. - **"the burn loop is the hotspot while memory tracks the same program"** (`profiler-fsharp-soak-cpu-e2e`): - Unchanged test. It failed two of three local runs before the fixture fix, both on "the burn loop is hot". - It passes three of three after it. - A direct engine trace puts `priceAll` on 100% of the burn thread's samples, up from 1.6%. - **The full `profiler` VS Code chunk passes locally on macOS against this branch: 323/323.** This PR's pipeline re-checks it on Linux and Windows, where the F# CPU and soak suites assert each named function's file. Closes #408.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TLDR
A CPU sample in code the compiler hid, such as an F#
match's decision tree, now keeps its method's file and takes the visible line before it. A function no longer splits into a located entry and an unlocated twin (#18).Details
Symbolizer.cs:FrameTable.Locatedkeeps TraceEvent'sfile:linewhen the line is visible. It asks the PDB instead when TraceEvent reports line 0,SequencePoint.HiddenLine, or no source at all (a prolog, IL −1). The answer is the method's nearest visible sequence point at or before the IL offset, or its first visible point when none comes before. A method with no visible point, such as a compiler-generated closure or a union constructor, stays unlocated as before.SymbolizerandFrameTablenow own oneSourceLocatorfor the whole recording, andCpuCommanddisposes it.SourceLocator.cs: the newLine(module, methodToken, ilOffset)reuses the per-module PDB cache that memory locations already use.MethodPointsreturns nothing for a token that names no method in the PDB.HotLeakAppFs/Matching.fsaddsMatching.classify, a non-inlined match over a struct active pattern whose tests compile to hidden sequence points. The line protocol gainsclassify <count>. Program.fs keeps its line numbers, so every existing line constant still holds.How Do The Automated Tests Prove It Works?
cpu_samples_in_fsharp_match_code_keep_their_file(F# e2e) profiles threeclassify 50000000rounds.HotLeakAppFs.Matching.classify(int32)came back twice. One entry held 948 samples (63.4%) in Matching.fs; the other was an unlocated twin of 179 samples (12.0%), all self time in the decision tree.run parse, 12 s),Csv.parseLinewent from 1592 located and 2507 unlocated samples to 4708 located and none unlocated. Underrun mixed, every named function is located. That split is what failed SharpLsp's Windows profiler suite on Razor review fixes (#399–#407) and Blazor and Razor docs, film and post SharpLsp#406 (run 39364686929).make fmt CHECK=1test:pathsmake lintmake test: 413/413 tests; lsp-profiler at 93.29% and lsp-profiler-dotnet at 92.60% against their 90% thresholdsmake buildCloses #18.