Skip to content

Commit 6f2e66a

Browse files
perf: avoid Seq allocations in hot markdown-parsing paths
- 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>
1 parent 01844d6 commit 6f2e66a

3 files changed

Lines changed: 13 additions & 6 deletions

File tree

RELEASE_NOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## [Unreleased]
44

55
### Changed
6+
* 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.
67
* 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.
78
* Replace deprecated `System.Net.WebClient` with `System.Net.Http.HttpClient` in the image downloader used by `--saveimages`. Removes the `#nowarn "44"` suppression.
89
* Bump `Newtonsoft.Json` transitive-dependency pin from 13.0.3 to 13.0.4.

src/Common/StringParsing.fs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ module String =
110110
lines
111111
|> Seq.choose (fun line ->
112112
if String.IsNullOrWhiteSpace line |> not then
113-
line |> Seq.takeWhile Char.IsWhiteSpace |> Seq.length |> Some
113+
line.Length - line.TrimStart().Length |> Some
114114
else
115115
None)
116116
|> fun xs -> if Seq.isEmpty xs then 0 else Seq.min xs
@@ -226,10 +226,16 @@ module StringPosition =
226226
let startAndRest = text.Substring(beforeStart.Length)
227227

228228
let startNum =
229-
Seq.windowed start.Length startAndRest
230-
|> Seq.map (fun chars -> System.String(chars))
231-
|> Seq.takeWhile ((=) start)
232-
|> Seq.length
229+
let mutable count = 0
230+
let mutable pos = 0
231+
let startLen = start.Length
232+
233+
while pos + startLen <= startAndRest.Length
234+
&& startAndRest.Substring(pos, startLen) = start do
235+
count <- count + 1
236+
pos <- pos + startLen
237+
238+
count
233239

234240
Some(
235241
startNum,

src/FSharp.Formatting.ApiDocs/XmlDocReader.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ module internal XmlDocReader =
133133
else
134134
let allLinesHaveSameColumn =
135135
nonEmptyLines
136-
|> Array.map (fun line -> line |> Seq.takeWhile (fun c -> c = ' ') |> Seq.length)
136+
|> Array.map (fun line -> line.Length - line.TrimStart([| ' ' |]).Length)
137137
|> Array.distinct
138138
|> Array.length
139139
|> (=) 1

0 commit comments

Comments
 (0)