diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 00000000..98628f6d --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-05-18 - [Terminal Search Fast-path Optimization] +**Learning:** Hoisting the first-character case-conversion out of the search hot loop `for_each_char_match_start` significantly reduces overhead. +**Action:** In search loops, perform the expensive `.to_ascii_lowercase()`/`.to_ascii_uppercase()` once before the loop, and use it inside the fast path. 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..8a115106 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,21 @@ fn for_each_char_match_start( return; } let first_needle = needle[0]; + 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. We can only short-circuit if BOTH the haystack character + // AND the needle character are ASCII. Non-ASCII characters might case-fold + // to ASCII (e.g., Kelvin sign U+212A folds to 'k'). + if h != first_lower && h != first_upper && h.is_ascii() && first_needle.is_ascii() { + index += 1; + continue; + } + if !chars_eq_ignore_case(h, first_needle) { index += 1; continue; }