diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 00000000..0a9c1878 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-03-12 - [Optimizing Terminal Scrollback Search] +**Learning:** [In Rust, optimizing text search hot loops (like terminal scrollback searches) can be achieved by extracting the first-character comparison out of the iterator chain and utilizing an ASCII fast-path. This avoids the overhead of setting up slice iterators, zipping, closures, and invoking heavy `chars_eq_ignore_case` functions for the vast majority of non-matches.] +**Action:** [When implementing case-insensitive character matching hot loops in Rust, hoist the target character's ASCII lower/uppercase conversions outside the loop, compare the iterated haystack characters against these bounds (`h != first_lower && h != first_upper`), but ONLY short-circuit reject if the haystack character is also ASCII (`h.is_ascii()`), since non-ASCII characters can map to ASCII when case-folded. Use full Unicode case-insensitive matching for the fallback.] diff --git a/crates/forktty-ui-gtk/src/gtk_app/terminal_search.rs b/crates/forktty-ui-gtk/src/gtk_app/terminal_search.rs index 90694aa2..bbb5833b 100644 --- a/crates/forktty-ui-gtk/src/gtk_app/terminal_search.rs +++ b/crates/forktty-ui-gtk/src/gtk_app/terminal_search.rs @@ -75,11 +75,24 @@ fn for_each_char_match_start( return; } let first_needle = needle[0]; + let first_is_ascii = first_needle.is_ascii(); + let first_lower = first_needle.to_ascii_lowercase(); + let first_upper = first_needle.to_ascii_uppercase(); + let mut index = 0; while index + needle.len() <= haystack.len() { + let h = haystack[index]; // Fast-path: short-circuit the full substring check if the first character - // doesn't match, avoiding iterator overhead in the common case. - if !chars_eq_ignore_case(haystack[index], first_needle) { + // doesn't match, avoiding function call overhead in the common case. + // We only short-circuit if both characters are ASCII. Non-ASCII characters + // (like the Kelvin sign) can fold to ASCII, so they must fall back to + // full Unicode comparison. + if first_is_ascii && h.is_ascii() && h != first_lower && h != first_upper { + index += 1; + continue; + } + + if !chars_eq_ignore_case(h, first_needle) { index += 1; continue; }