This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-23762-f33dcec6dfd0b900ddf89b75b46e547f7923c0e7 in repository https://gitbox.apache.org/repos/asf/datafusion.git
commit e271f65c9f8188cb6b1876da099646f396e34e00 Author: Andy Grove <[email protected]> AuthorDate: Wed Jul 22 10:32:56 2026 -0600 perf: optimize left_right in datafusion-functions (#23762) ## Which issue does this PR close? N/A ## Rationale for this change Optimize existing expression. ## What changes are included in this PR? Added an ASCII fast path to left_right_byte_length so left/right compute the n-th codepoint byte offset via O(1) arithmetic instead of a per-row char_indices() UTF-8 scan. ## Are these changes tested? Existing tests. Benchmark (criterion): - string_view short_result: 4.848% faster (base 56784ns -> cand 54031ns) - string long_result: 38.098% faster (base 141926ns -> cand 87855ns) - string_view long_result: 44.817% faster (base 94346ns -> cand 52063ns) - string short_result: 13.184% faster (base 88532ns -> cand 76860ns) - string_view short_result: 15.165% faster (base 57136ns -> cand 48472ns) - string long_result: 39.278% faster (base 140969ns -> cand 85600ns) - string_view long_result: 40.29% faster (base 81808ns -> cand 48847ns) - string short_result: 14.369% faster (base 86809ns -> cand 74336ns) Full criterion output: ```text left/string short_result time: [73.406 µs 73.726 µs 74.067 µs] change: [−15.026% −14.369% −13.742%] (p = 0.00 < 0.05) Performance has improved. Found 1 outliers among 100 measurements (1.00%) 1 (1.00%) low mild left/string long_result time: [85.227 µs 85.519 µs 85.827 µs] change: [−39.588% −39.278% −38.969%] (p = 0.00 < 0.05) Performance has improved. Found 3 outliers among 100 measurements (3.00%) 1 (1.00%) low mild 2 (2.00%) high mild left/string_view short_result time: [48.206 µs 48.411 µs 48.613 µs] change: [−17.549% −15.165% −13.033%] (p = 0.00 < 0.05) Performance has improved. left/string_view long_result time: [48.383 µs 48.691 µs 49.038 µs] change: [−40.884% −40.290% −39.714%] (p = 0.00 < 0.05) Performance has improved. right/string short_result time: [76.804 µs 76.986 µs 77.159 µs] change: [−13.955% −13.184% −12.465%] (p = 0.00 < 0.05) Performance has improved. Found 6 outliers among 100 measurements (6.00%) 3 (3.00%) low severe 3 (3.00%) low mild right/string long_result time: [88.219 µs 88.597 µs 88.938 µs] change: [−38.351% −38.098% −37.853%] (p = 0.00 < 0.05) Performance has improved. right/string_view short_result time: [53.380 µs 53.545 µs 53.728 µs] change: [−5.5061% −4.8482% −4.1872%] (p = 0.00 < 0.05) Performance has improved. Found 3 outliers among 100 measurements (3.00%) 3 (3.00%) high mild right/string_view long_result time: [52.049 µs 52.332 µs 52.633 µs] change: [−45.883% −44.817% −43.850%] (p = 0.00 < 0.05) Performance has improved. Found 6 outliers among 100 measurements (6.00%) 6 (6.00%) high mild ``` ## Are there any user-facing changes? No <!-- If there are user-facing changes then we may require documentation to be updated before approving the PR. --> <!-- If there are any breaking changes to public APIs, please add the `api change` label. --> --- datafusion/functions/src/unicode/common.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/datafusion/functions/src/unicode/common.rs b/datafusion/functions/src/unicode/common.rs index 9f91e4f1b0..5dc8f334da 100644 --- a/datafusion/functions/src/unicode/common.rs +++ b/datafusion/functions/src/unicode/common.rs @@ -125,16 +125,22 @@ pub(crate) enum StringCharLen { /// Calculate the byte length of the substring of `n` chars from string `string` #[inline] fn left_right_byte_length(string: &str, n: i64) -> usize { + let abs = n.unsigned_abs().min(usize::MAX as u64) as usize; + // For ASCII input every character is exactly one byte, so the byte offset of + // the n-th codepoint is just the (clamped) character count. This avoids the + // per-character `char_indices()` scan of the general path. match n.cmp(&0) { + Ordering::Equal => 0, + // `abs` chars trimmed from the end: keep the leading `len - abs`. + Ordering::Less if string.is_ascii() => string.len().saturating_sub(abs), Ordering::Less => string .char_indices() - .nth_back((n.unsigned_abs().min(usize::MAX as u64) - 1) as usize) + .nth_back(abs - 1) .map(|(index, _)| index) .unwrap_or(0), - Ordering::Equal => 0, - Ordering::Greater => { - byte_offset_of_char(string, n.unsigned_abs().min(usize::MAX as u64) as usize) - } + // First `abs` chars, but never past the end of the string. + Ordering::Greater if string.is_ascii() => abs.min(string.len()), + Ordering::Greater => byte_offset_of_char(string, abs), } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
