From 3247b4fbcb08f9842006efa933b4533f60ba41ca Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 12 Apr 2026 04:55:30 +0000 Subject: [PATCH 1/2] perf: StringBuilder in CSS readString; AsSpan in JsonStringEncodeTo on .NET 6+ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - HtmlCssSelectors: replace O(n²) string concatenation in readString with StringBuilder accumulator, reducing allocations for CSS selector parsing - JsonValue: use TextWriter.Write(ReadOnlySpan) instead of Substring in JsonStringEncodeTo when targeting .NET 6+ (NETSTANDARD2_0 fallback kept) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/FSharp.Data.Html.Core/HtmlCssSelectors.fs | 24 ++++++++++++------- src/FSharp.Data.Json.Core/JsonValue.fs | 10 +++++++- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/FSharp.Data.Html.Core/HtmlCssSelectors.fs b/src/FSharp.Data.Html.Core/HtmlCssSelectors.fs index ff8e9cead..15749d7f9 100644 --- a/src/FSharp.Data.Html.Core/HtmlCssSelectors.fs +++ b/src/FSharp.Data.Html.Core/HtmlCssSelectors.fs @@ -59,7 +59,7 @@ module internal HtmlCssSelectors = (isHexadecimalDigit || c = '\n' || c = '\f' || c = '\r') |> not - let rec readString acc = + let rec readStringIntoSb (acc: System.Text.StringBuilder) = function | c :: t when Char.IsLetterOrDigit(c) @@ -68,20 +68,28 @@ module internal HtmlCssSelectors = || c.Equals('+') || c.Equals('/') -> - readString (acc + (c.ToString())) t + acc.Append(c) |> ignore + readStringIntoSb acc t | '\'' :: t -> if inQuotes then inQuotes <- false - acc, t + acc.ToString(), t else inQuotes <- true - readString acc t - | '\\' :: c :: t when isCharacterEscapable c -> readString (acc + (c.ToString())) t - | c :: t when inQuotes -> readString (acc + (c.ToString())) t - | c :: t -> acc, c :: t - | [] -> acc, [] + readStringIntoSb acc t + | '\\' :: c :: t when isCharacterEscapable c -> + acc.Append(c) |> ignore + readStringIntoSb acc t + | c :: t when inQuotes -> + acc.Append(c) |> ignore + readStringIntoSb acc t + | c :: t -> acc.ToString(), c :: t + | [] -> acc.ToString(), [] | c -> failwithf "Invalid css selector syntax at: %s" (new String(Array.ofList c)) + let readString (initial: string) chars = + readStringIntoSb (System.Text.StringBuilder(initial)) chars + let (|StartsWith|_|) (s: string) (items: char list) = let candidates = s.ToCharArray() diff --git a/src/FSharp.Data.Json.Core/JsonValue.fs b/src/FSharp.Data.Json.Core/JsonValue.fs index 21be4f008..0ae390387 100644 --- a/src/FSharp.Data.Json.Core/JsonValue.fs +++ b/src/FSharp.Data.Json.Core/JsonValue.fs @@ -165,9 +165,13 @@ type JsonValue = || c = '\\' if needsEscaping then - // Write all accumulated unescaped characters in one operation using Substring + // Write all accumulated unescaped characters in one operation if i > lastWritePos then +#if NETSTANDARD2_0 w.Write(value.Substring(lastWritePos, i - lastWritePos)) +#else + w.Write(value.AsSpan(lastWritePos, i - lastWritePos)) +#endif // Write the escaped character if ci >= 0 && ci <= 7 || ci = 11 || ci >= 14 && ci <= 31 then @@ -187,7 +191,11 @@ type JsonValue = // Write any remaining unescaped characters if lastWritePos < value.Length then +#if NETSTANDARD2_0 w.Write(value.Substring(lastWritePos)) +#else + w.Write(value.AsSpan(lastWritePos)) +#endif /// Serializes this JsonValue to a string with the specified formatting options. /// Controls formatting: indented, compact, or compact with spaces. From c0b01e057e967248140bf0a49a09acbc65090ea5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 12 Apr 2026 04:55:32 +0000 Subject: [PATCH 2/2] ci: trigger checks