From 67a113e1b9771b573424adcea26f2d0a8e16de4d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 04:39:07 +0000 Subject: [PATCH 1/2] perf: avoid ToCharArray allocations in TextConversions, HtmlParser, HtmlCssSelectors, HtmlOperations - TextConversions.RemoveAdorners: replace ToCharArray()+Array.filter with a StringBuilder loop; avoids two intermediate char[] allocations (ToCharArray result + filtered result) on the hot path for every number/date parse attempt - HtmlParser: replace ToCharArray()|>Array.rev string reversal with Array.init using direct index arithmetic; avoids ToCharArray allocation for malformed end-tag detection (e.g.
  • ) - HtmlCssSelectors.StartsWith: remove ToCharArray(); compare list against string directly via Seq.compareWith (string is IEnumerable) - HtmlCssSelectors.TokenStr: replace ToCharArray()|>Array.toList with List.ofSeq - HtmlCssSelectors.Tokenize: replace ToCharArray()|>Array.toList with List.ofSeq - HtmlOperations AttributeContainsPrefix: remove ToCharArray() before Seq.skipWhile since string is already IEnumerable Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/FSharp.Data.Html.Core/HtmlCssSelectors.fs | 12 +++++------- src/FSharp.Data.Html.Core/HtmlOperations.fs | 2 +- src/FSharp.Data.Html.Core/HtmlParser.fs | 5 ++++- .../TextConversions.fs | 13 ++++++++----- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/FSharp.Data.Html.Core/HtmlCssSelectors.fs b/src/FSharp.Data.Html.Core/HtmlCssSelectors.fs index ff8e9cead..13bb0db24 100644 --- a/src/FSharp.Data.Html.Core/HtmlCssSelectors.fs +++ b/src/FSharp.Data.Html.Core/HtmlCssSelectors.fs @@ -83,20 +83,18 @@ module internal HtmlCssSelectors = | c -> failwithf "Invalid css selector syntax at: %s" (new String(Array.ofList c)) let (|StartsWith|_|) (s: string) (items: char list) = - let candidates = s.ToCharArray() - - if items.Length < candidates.Length then + if items.Length < s.Length then None else - let start = items |> Seq.take (candidates.Length) |> Seq.toList + let start = items |> Seq.take s.Length - if (Seq.compareWith (fun a b -> (int a) - (int b)) start candidates) = 0 then + if (Seq.compareWith (fun a b -> (int a) - (int b)) start s) = 0 then Some(items |> Seq.skip s.Length |> Seq.toList) else None let (|TokenStr|_|) (s: string) x = - let chars = s.ToCharArray() |> Array.toList + let chars = List.ofSeq s let rec equal x s = match x, s with @@ -208,7 +206,7 @@ module internal HtmlCssSelectors = member public x.Tokenize(pCssSelector: string) = cssSelector <- pCssSelector - source <- cssSelector.ToCharArray() |> Array.toList + source <- List.ofSeq cssSelector charCount <- source.Length tokenize () diff --git a/src/FSharp.Data.Html.Core/HtmlOperations.fs b/src/FSharp.Data.Html.Core/HtmlOperations.fs index 7a172ca33..4f6fb3246 100644 --- a/src/FSharp.Data.Html.Core/HtmlOperations.fs +++ b/src/FSharp.Data.Html.Core/HtmlOperations.fs @@ -367,7 +367,7 @@ module HtmlNode = let selectedNodes = filterByAttr level acc name (fun v -> let chars = - v.ToCharArray() + v |> Seq.skipWhile (fun c -> c = '\'') |> Seq.takeWhile Char.IsLetter |> Seq.toArray diff --git a/src/FSharp.Data.Html.Core/HtmlParser.fs b/src/FSharp.Data.Html.Core/HtmlParser.fs index 011f2986d..2c83a391d 100644 --- a/src/FSharp.Data.Html.Core/HtmlParser.fs +++ b/src/FSharp.Data.Html.Core/HtmlParser.fs @@ -1113,7 +1113,10 @@ module internal HtmlParser = parse' docType elements expectedTagEnd parentTagName (TagEnd expectedTagEnd :: tokens) | TagEnd name :: rest when name <> expectedTagEnd - && (name <> (new String(expectedTagEnd.ToCharArray() |> Array.rev))) + && (name + <> (new String( + Array.init expectedTagEnd.Length (fun i -> expectedTagEnd.[expectedTagEnd.Length - 1 - i]) + ))) -> // ignore this token if not the expected end tag (or it's reverse, eg:
  • ) parse' docType elements expectedTagEnd parentTagName rest diff --git a/src/FSharp.Data.Runtime.Utilities/TextConversions.fs b/src/FSharp.Data.Runtime.Utilities/TextConversions.fs index 9ff5d65ed..798b0b8e5 100644 --- a/src/FSharp.Data.Runtime.Utilities/TextConversions.fs +++ b/src/FSharp.Data.Runtime.Utilities/TextConversions.fs @@ -104,11 +104,14 @@ type TextConversions private () = // No adorners found, return original string to avoid allocation value else - // Adorners found, perform filtering - String( - value.ToCharArray() - |> Array.filter (not << TextConversions.DefaultRemovableAdornerCharacters.Contains) - ) + // Adorners found, perform filtering via StringBuilder to avoid ToCharArray allocation + let sb = System.Text.StringBuilder(value.Length) + + for c in value do + if not (TextConversions.DefaultRemovableAdornerCharacters.Contains(c)) then + sb.Append(c) |> ignore + + sb.ToString() /// Turns empty or null string value into None, otherwise returns Some static member AsString str = From 76236007886df6860ad4ccc394aed18283d12724 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 9 Apr 2026 04:39:09 +0000 Subject: [PATCH 2/2] ci: trigger checks