alamb commented on code in PR #23540: URL: https://github.com/apache/datafusion/pull/23540#discussion_r3588903929
########## datafusion/functions/benches/regexp_instr.rs: ########## @@ -0,0 +1,99 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use arrow::array::Int64Array; +use arrow::array::OffsetSizeTrait; +use arrow::datatypes::{DataType, Field}; +use arrow::util::bench_util::create_string_array_with_len; +use criterion::{Criterion, criterion_group, criterion_main}; +use datafusion_common::config::ConfigOptions; +use datafusion_common::{DataFusionError, ScalarValue}; +use datafusion_expr::{ColumnarValue, ScalarFunctionArgs}; +use datafusion_functions::regex; +use std::hint::black_box; +use std::sync::Arc; + +fn create_args<O: OffsetSizeTrait>( Review Comment: FWIW @andygrove -- it would make it easier to verify these results if you made one PR to add the benchmark and then a second PR to make the code change That way we can use the `run benchmark <benchmark>` bot command to get a second performance run ########## datafusion/functions/src/regex/regexpinstr.rs: ########## @@ -286,120 +286,104 @@ fn regexp_instr_inner<'a, S>( regex_array: &S, start_array: Option<&Int64Array>, nth_array: Option<&Int64Array>, - flags_array: Option<S>, + flags_array: Option<&S>, subexp_array: Option<&Int64Array>, ) -> Result<ArrayRef, ArrowError> where S: StringArrayType<'a>, { let len = values.len(); + let mut regex_cache = RegexCache::default(); + let mut result = Int64Builder::with_capacity(len); - let default_start_array = PrimitiveArray::<Int64Type>::from(vec![1; len]); - let start_array = start_array.unwrap_or(&default_start_array); - let start_input: Vec<i64> = (0..start_array.len()) - .map(|i| start_array.value(i)) // handle nulls as 0 - .collect(); - - let default_nth_array = PrimitiveArray::<Int64Type>::from(vec![1; len]); - let nth_array = nth_array.unwrap_or(&default_nth_array); - let nth_input: Vec<i64> = (0..nth_array.len()) - .map(|i| nth_array.value(i)) // handle nulls as 0 - .collect(); - - let flags_input = match flags_array { - Some(flags) => flags.iter().collect(), - None => vec![None; len], - }; + for i in 0..len { + if regex_array.is_null(i) { + result.append_null(); + continue; + } + let regex = regex_array.value(i); + if regex.is_empty() { + result.append_value(0); + continue; + } - let default_subexp_array = PrimitiveArray::<Int64Type>::from(vec![0; len]); - let subexp_array = subexp_array.unwrap_or(&default_subexp_array); - let subexp_input: Vec<i64> = (0..subexp_array.len()) - .map(|i| subexp_array.value(i)) // handle nulls as 0 - .collect(); - - let mut regex_cache = HashMap::new(); - - let result: Result<Vec<Option<i64>>, ArrowError> = izip!( - values.iter(), - regex_array.iter(), - start_input.iter(), - nth_input.iter(), - flags_input.iter(), - subexp_input.iter() - ) - .map(|(value, regex, start, nth, flags, subexp)| match regex { - None => Ok(None), - Some("") => Ok(Some(0)), - Some(regex) => get_index( - value, - regex, - *start, - *nth, - *subexp, - *flags, - &mut regex_cache, - ), - }) - .collect(); - Ok(Arc::new(Int64Array::from(result?))) -} + if values.is_null(i) { + result.append_null(); + continue; + } + let value = values.value(i); + if value.is_empty() { + result.append_value(0); + continue; + } -fn handle_subexp( - pattern: &Regex, - search_slice: &str, - subexpr: i64, - value: &str, - byte_start_offset: usize, -) -> Result<Option<i64>, ArrowError> { - if let Some(captures) = pattern.captures(search_slice) - && let Some(matched) = captures.get(subexpr as usize) - { - // Convert byte offset relative to search_slice back to 1-based character offset - // relative to the original `value` string. - let start_char_offset = - value[..byte_start_offset + matched.start()].chars().count() as i64 + 1; - return Ok(Some(start_char_offset)); + let flags = match flags_array { + Some(flags) if !flags.is_null(i) => Some(flags.value(i)), + _ => None, + }; + let pattern = regex_cache.get_or_compile(regex, flags)?; + + // The defaults apply when the optional argument was not supplied at + // all. A supplied but null slot reads through as its raw buffer value. + let start = start_array.map_or(1, |array| array.value(i)); + let nth = nth_array.map_or(1, |array| array.value(i)); + let subexp = subexp_array.map_or(0, |array| array.value(i)); + + result.append_value(get_index(value, pattern, start, nth, subexp)?); } - Ok(Some(0)) // Return 0 if the subexpression was not found + + Ok(Arc::new(result.finish())) } -fn get_nth_match( - pattern: &Regex, - search_slice: &str, - n: i64, - byte_start_offset: usize, - value: &str, -) -> Result<Option<i64>, ArrowError> { - if let Some(mat) = pattern.find_iter(search_slice).nth((n - 1) as usize) { - // Convert byte offset relative to search_slice back to 1-based character offset - // relative to the original `value` string. - let match_start_byte_offset = byte_start_offset + mat.start(); - let match_start_char_offset = - value[..match_start_byte_offset].chars().count() as i64 + 1; - Ok(Some(match_start_char_offset)) - } else { - Ok(Some(0)) // Return 0 if the N-th match was not found +/// Compiles the patterns seen so far, keyed by `(pattern, flags)`. Review Comment: this looks similar to https://github.com/apache/datafusion/blob/998f534aafbf55c83daaa6fd4985ba143954b0e0/datafusion/functions/src/regex/mod.rs#L131-L130 Maybe we could (as a follow on PR) extract the common regexp caching functionality) -- I personally prefer this Cache pattern -- for use in the other functions with regular expressions -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
