From abaf9a4009d2a0f909c5feb9fb2e99ada7349219 Mon Sep 17 00:00:00 2001 From: Ivandro Jao Date: Sun, 19 Jul 2026 12:47:19 +0100 Subject: [PATCH 1/2] Utilities: single-lookup htmlTags updates in auto-break tag stripping AutoBreakLineMoreThanTwoLines and AutoBreakLinePrivate collected stripped tags with ContainsKey + get + set - up to three hash lookups per tag inside per-character scanning loops. Route all three sites through an AddOrAppendHtmlTag helper using CollectionsMarshal.GetValueRefOrAddDefault on .NET 8+ (one lookup; null + tag handles the add case), with a TryGetValue fallback for netstandard2.1. Co-Authored-By: Claude Fable 5 --- src/libse/Common/Utilities.cs | 44 ++++++++++++++++------------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/src/libse/Common/Utilities.cs b/src/libse/Common/Utilities.cs index 2699966382..0d06339166 100644 --- a/src/libse/Common/Utilities.cs +++ b/src/libse/Common/Utilities.cs @@ -345,14 +345,7 @@ public static string AutoBreakLineMoreThanTwoLines(string text, int maximumLengt { string tag = s.Substring(six, endIndex - six + 1); s = s.Remove(six, tag.Length); - if (htmlTags.ContainsKey(six)) - { - htmlTags[six] = htmlTags[six] + tag; - } - else - { - htmlTags.Add(six, tag); - } + AddOrAppendHtmlTag(htmlTags, six, tag); } else { @@ -434,6 +427,23 @@ public static string AutoBreakLineMoreThanTwoLines(string text, int maximumLengt return text; } + private static void AddOrAppendHtmlTag(Dictionary htmlTags, int index, string tag) + { +#if NET8_0_OR_GREATER + ref var existing = ref System.Runtime.InteropServices.CollectionsMarshal.GetValueRefOrAddDefault(htmlTags, index, out _); + existing += tag; // null for a newly added key, so this both adds and appends +#else + if (htmlTags.TryGetValue(index, out var existing)) + { + htmlTags[index] = existing + tag; + } + else + { + htmlTags.Add(index, tag); + } +#endif + } + private static string ReInsertHtmlTagsAndCleanUp(string input, Dictionary htmlTags) { var s = ReInsertHtmlTags(input, htmlTags); @@ -604,14 +614,7 @@ public static string AutoBreakLinePrivate(string input, int maximumLength, int m if (endIndexAssTag > 0) { tagString = tagString.Substring(0, endIndexAssTag); - if (htmlTags.ContainsKey(six)) - { - htmlTags[six] = htmlTags[six] + tagString; - } - else - { - htmlTags.Add(six, tagString); - } + AddOrAppendHtmlTag(htmlTags, six, tagString); s = s.Remove(six, endIndexAssTag); continue; @@ -628,14 +631,7 @@ public static string AutoBreakLinePrivate(string input, int maximumLength, int m { var tag = s.Substring(six, endIndex - six + 1); s = s.Remove(six, tag.Length); - if (htmlTags.ContainsKey(six)) - { - htmlTags[six] += tag; - } - else - { - htmlTags.Add(six, tag); - } + AddOrAppendHtmlTag(htmlTags, six, tag); } else { From b8c1c81737b754fc9a7fea2fcb1e812dcc7d2722 Mon Sep 17 00:00:00 2001 From: Nikolaj Olsson Date: Sun, 19 Jul 2026 14:30:52 +0200 Subject: [PATCH 2/2] Utilities: drop the CollectionsMarshal path from AddOrAppendHtmlTag Benchmarking the auto-break tag-stripping loop showed no measurable gain from the GetValueRefOrAddDefault path (tag-heavy line 3031ns -> 3074ns, html 1182ns -> 1116ns, plain 392ns -> 388ns, identical allocations). The loop is dominated by the s.Remove() per tag, which reallocates and copies the whole string, so the dictionary update is rounding error. Keep the helper - it still removes three duplicated blocks - but with a single TryGetValue body instead of two framework-conditional paths. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/libse/Common/Utilities.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/libse/Common/Utilities.cs b/src/libse/Common/Utilities.cs index 0d06339166..ac1311a91c 100644 --- a/src/libse/Common/Utilities.cs +++ b/src/libse/Common/Utilities.cs @@ -429,10 +429,6 @@ public static string AutoBreakLineMoreThanTwoLines(string text, int maximumLengt private static void AddOrAppendHtmlTag(Dictionary htmlTags, int index, string tag) { -#if NET8_0_OR_GREATER - ref var existing = ref System.Runtime.InteropServices.CollectionsMarshal.GetValueRefOrAddDefault(htmlTags, index, out _); - existing += tag; // null for a newly added key, so this both adds and appends -#else if (htmlTags.TryGetValue(index, out var existing)) { htmlTags[index] = existing + tag; @@ -441,7 +437,6 @@ private static void AddOrAppendHtmlTag(Dictionary htmlTags, int ind { htmlTags.Add(index, tag); } -#endif } private static string ReInsertHtmlTagsAndCleanUp(string input, Dictionary htmlTags)