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