From 6f2e66a70cdfeee6b69bbdc8b8417e0ff0a9ef4e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 10:52:28 +0000 Subject: [PATCH 1/2] perf: avoid Seq allocations in hot markdown-parsing paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - removeSpaces: replace Seq.takeWhile+Seq.length per line with String.TrimStart().Length — avoids boxing each char and allocating two enumerators per non-empty line. - StartsWithNTimesTrimIgnoreStartWhitespace: replace Seq.windowed + Seq.map String + Seq.takeWhile + Seq.length with a direct index loop — avoids O(n) sliding-window allocations just to count consecutive fence characters (e.g. backticks or tildes) at the start of a line. Called for every line during markdown block parsing. - XmlDocReader.readXmlElementAsSingleSummary: same Seq.takeWhile fix when checking indentation columns of XML doc comment lines. All 520 tests pass (317 Markdown, 143 Literate, 30 CodeFormat, 30 ApiDocs). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- RELEASE_NOTES.md | 1 + src/Common/StringParsing.fs | 16 +++++++++++----- src/FSharp.Formatting.ApiDocs/XmlDocReader.fs | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index e54bd9b27..82fc5b935 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -3,6 +3,7 @@ ## [Unreleased] ### Changed +* Avoid `Seq` allocations in hot markdown-parsing paths. `removeSpaces` (called on every XML doc comment and literate code block) previously iterated each line character-by-character via `Seq.takeWhile + Seq.length`; it now uses `String.TrimStart().Length`. The `StartsWithNTimesTrimIgnoreStartWhitespace` active pattern (called for every line to detect fenced code blocks) previously built a sliding-window `Seq`, allocated a `System.String` per window, and counted matches via `Seq.length`; it now uses a simple index loop. * Compile `Regex` instances to module-level singletons (with `RegexOptions.Compiled`) in `PageContentList`, `HtmlFormatting`, `Formatting`, `Menu`, and `LlmsTxt`. Previously a new, uncompiled `Regex` was constructed on every call (once per page heading, once per HTML page, once per menu item, once per llms.txt entry), incurring repeated JIT overhead. The patterns are now compiled once at module load and reused across all calls. * Replace deprecated `System.Net.WebClient` with `System.Net.Http.HttpClient` in the image downloader used by `--saveimages`. Removes the `#nowarn "44"` suppression. * Bump `Newtonsoft.Json` transitive-dependency pin from 13.0.3 to 13.0.4. diff --git a/src/Common/StringParsing.fs b/src/Common/StringParsing.fs index 1136bd4cc..bc18954f6 100644 --- a/src/Common/StringParsing.fs +++ b/src/Common/StringParsing.fs @@ -110,7 +110,7 @@ module String = lines |> Seq.choose (fun line -> if String.IsNullOrWhiteSpace line |> not then - line |> Seq.takeWhile Char.IsWhiteSpace |> Seq.length |> Some + line.Length - line.TrimStart().Length |> Some else None) |> fun xs -> if Seq.isEmpty xs then 0 else Seq.min xs @@ -226,10 +226,16 @@ module StringPosition = let startAndRest = text.Substring(beforeStart.Length) let startNum = - Seq.windowed start.Length startAndRest - |> Seq.map (fun chars -> System.String(chars)) - |> Seq.takeWhile ((=) start) - |> Seq.length + let mutable count = 0 + let mutable pos = 0 + let startLen = start.Length + + while pos + startLen <= startAndRest.Length + && startAndRest.Substring(pos, startLen) = start do + count <- count + 1 + pos <- pos + startLen + + count Some( startNum, diff --git a/src/FSharp.Formatting.ApiDocs/XmlDocReader.fs b/src/FSharp.Formatting.ApiDocs/XmlDocReader.fs index 979f1d3bc..8acd3ab06 100644 --- a/src/FSharp.Formatting.ApiDocs/XmlDocReader.fs +++ b/src/FSharp.Formatting.ApiDocs/XmlDocReader.fs @@ -133,7 +133,7 @@ module internal XmlDocReader = else let allLinesHaveSameColumn = nonEmptyLines - |> Array.map (fun line -> line |> Seq.takeWhile (fun c -> c = ' ') |> Seq.length) + |> Array.map (fun line -> line.Length - line.TrimStart([| ' ' |]).Length) |> Array.distinct |> Array.length |> (=) 1 From 7bf697c7de9d733dcd66a4360f832861081ce111 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 17 Apr 2026 10:52:30 +0000 Subject: [PATCH 2/2] ci: trigger checks