From 897dc61a857a459d1b125f4ed0c41851a744f111 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 25 Sep 2026 00:40:01 +0000 Subject: [PATCH] perf: avoid Seq.windowed/String allocations in fence-count active pattern StartsWithNTimesTrimIgnoreStartWhitespace (used to count repeated fence characters like backtick/tilde runs when parsing code fences and setext headers) previously counted matches via Seq.windowed + String() + Seq.map, allocating one string per character offset of the scanned text. Replace this with a direct String.CompareOrdinal loop over string offsets, with no intermediate allocations. Behavior (including counting semantics) is unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- RELEASE_NOTES.md | 5 +++++ src/Common/StringParsing.fs | 20 ++++++++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 9e0162bfb..260552393 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,10 @@ # Changelog +## [Unreleased] + +### Changed +* Avoided a per-character `Seq.windowed`/`String()` allocation in `StartsWithNTimesTrimIgnoreStartWhitespace`, the active pattern used to count repeated fence characters (`` ` `` / `~`) when parsing code fences and headers. The count is now computed with `String.CompareOrdinal` over string offsets, with no intermediate substring allocations. Behavior is unchanged. + ## [23.0.0-alpha.8] - 2026-09-18 ### Added diff --git a/src/Common/StringParsing.fs b/src/Common/StringParsing.fs index 27ec806cf..af4d7165c 100644 --- a/src/Common/StringParsing.fs +++ b/src/Common/StringParsing.fs @@ -232,11 +232,23 @@ module StringPosition = if String.IsNullOrWhiteSpace(beforeStart) then let startAndRest = text.Substring(beforeStart.Length) + // Count matches of `start` at consecutive offsets 0, 1, 2, ... in `startAndRest` + // (same semantics as the previous Seq.windowed-based implementation), without + // allocating a substring per offset. 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 keepGoing = true + + while keepGoing do + if + count + start.Length <= startAndRest.Length + && String.CompareOrdinal(startAndRest, count, start, 0, start.Length) = 0 + then + count <- count + 1 + else + keepGoing <- false + + count Some( startNum,