This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion.git
The following commit(s) were added to refs/heads/main by this push:
new e271f65c9f perf: optimize left_right in datafusion-functions (#23762)
e271f65c9f is described below
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]