diff --git a/.jules/bolt.md b/.jules/bolt.md index 3644ef5..48e702b 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -6,3 +6,7 @@ **Learning:** In `split_segments`, constructing `Segment` previously performed `text.to_owned()` for every split segment. This caused unnecessary memory allocation, as the parsed segment string could just borrow from the original input `&str`. **Action:** Update parsing intermediate structs (like `Segment`) to carry string slices (`&'a str`) representing chunks of the input string rather than owning `String`s when they are only used briefly to route segments to transformation parsers. + +## 2024-05-18 - [Avoid UTF-8 overhead for ASCII search in parsing] +**Learning:** In RockQL's parser, iterating with `.char_indices()` to search for simple ASCII characters (like `'|'` or whitespace) adds unnecessary UTF-8 decoding overhead. This is a noticeable bottleneck for long string processing. +**Action:** Use string methods that operate at the byte level directly, such as `.match_indices()` or `.as_bytes().iter().position()`, to avoid the overhead of decoding UTF-8 when searching for ASCII markers or boundaries. diff --git a/compiler/rockql-parser/src/lib.rs b/compiler/rockql-parser/src/lib.rs index 1ddfaa9..12c1b50 100644 --- a/compiler/rockql-parser/src/lib.rs +++ b/compiler/rockql-parser/src/lib.rs @@ -70,16 +70,16 @@ fn split_segments(source: &str) -> Vec> { for (line_index, line) in source.lines().enumerate() { let mut start = 0; - for (byte_index, character) in line.char_indices() { - if character == '|' { - push_segment( - &mut segments, - &line[start..byte_index], - line_index + 1, - start, - ); - start = byte_index + character.len_utf8(); - } + // ⚡ Bolt Optimization: Use `match_indices` instead of `char_indices` for ASCII search + // This avoids UTF-8 decoding overhead when searching for the pipe character. + for (byte_index, _) in line.match_indices('|') { + push_segment( + &mut segments, + &line[start..byte_index], + line_index + 1, + start, + ); + start = byte_index + 1; // '|' is 1 byte } push_segment(&mut segments, &line[start..], line_index + 1, start); @@ -102,9 +102,12 @@ fn push_segment<'a>(segments: &mut Vec>, raw: &'a str, line: usize, } fn parse_transform(text: &str, span: Span) -> Result { + // ⚡ Bolt Optimization: Use `.as_bytes().iter().position` to avoid UTF-8 decoding + // overhead when looking for ASCII whitespace to determine the keyword end. let keyword_end = text - .char_indices() - .find_map(|(index, character)| character.is_whitespace().then_some(index)) + .as_bytes() + .iter() + .position(|&b| b.is_ascii_whitespace()) .unwrap_or(text.len()); let keyword = &text[..keyword_end];