This is an automated email from the ASF dual-hosted git repository.
andygrove pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-comet.git
The following commit(s) were added to refs/heads/main by this push:
new 9483db47f0 perf: vectorize `spark_unscaled_value` (9x faster) (#4972)
9483db47f0 is described below
commit 9483db47f04293b3639a9df14c044194e19ed4c5
Author: kid <[email protected]>
AuthorDate: Mon Jul 20 22:34:39 2026 +0800
perf: vectorize `spark_unscaled_value` (9x faster) (#4972)
Replace the per-element Int64Builder loop in the array branch with the
vectorized Arrow unary kernel: the Decimal128 values buffer is mapped to
Int64 in a single pass and the null buffer is carried over untouched
(zero-copy). The scalar branch is unchanged.
Benchmark (8192 rows, Decimal128(20,2), benches/unscaled_value.rs):
- no nulls: 29.5 us -> 3.2 us (~9.3x)
- sparse nulls (1 in 10): 35.5 us -> 3.0 us (~11.7x)
- dense nulls (1 in 2): 30.5 us -> 2.9 us (~10.4x)
Part of #4942
---
native/spark-expr/Cargo.toml | 4 ++
native/spark-expr/benches/unscaled_value.rs | 62 +++++++++++++++++++++
.../src/math_funcs/internal/unscaled_value.rs | 63 ++++++++++++++++++----
3 files changed, 120 insertions(+), 9 deletions(-)
diff --git a/native/spark-expr/Cargo.toml b/native/spark-expr/Cargo.toml
index 9f98b4fcb6..0c34bb7810 100644
--- a/native/spark-expr/Cargo.toml
+++ b/native/spark-expr/Cargo.toml
@@ -167,3 +167,7 @@ harness = false
[[bench]]
name = "check_overflow"
harness = false
+
+[[bench]]
+name = "unscaled_value"
+harness = false
diff --git a/native/spark-expr/benches/unscaled_value.rs
b/native/spark-expr/benches/unscaled_value.rs
new file mode 100644
index 0000000000..e4348add40
--- /dev/null
+++ b/native/spark-expr/benches/unscaled_value.rs
@@ -0,0 +1,62 @@
+// 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::{ArrayRef, Decimal128Array};
+use criterion::{criterion_group, criterion_main, Criterion};
+use datafusion::physical_plan::ColumnarValue;
+use datafusion_comet_spark_expr::spark_unscaled_value;
+use std::hint::black_box;
+use std::sync::Arc;
+
+/// Build a Decimal128(20, 2) column of `rows` rows, with every `null_every`-th
+/// row null (`null_every == 0` means no nulls).
+fn create_decimal_array(rows: usize, null_every: usize) -> ArrayRef {
+ let arr: Decimal128Array = (0..rows)
+ .map(|i| {
+ if null_every != 0 && i % null_every == 0 {
+ None
+ } else {
+ Some((i as i128 % 100_000) * 100)
+ }
+ })
+ .collect::<Decimal128Array>()
+ .with_precision_and_scale(20, 2)
+ .unwrap();
+ Arc::new(arr)
+}
+
+fn criterion_benchmark(c: &mut Criterion) {
+ let rows = 8192;
+
+ let mut bench = |name: &str, arr: &ArrayRef| {
+ let args = vec![ColumnarValue::Array(Arc::clone(arr))];
+ c.bench_function(name, |b| {
+ b.iter(||
black_box(spark_unscaled_value(black_box(&args)).unwrap()))
+ });
+ };
+
+ let no_nulls = create_decimal_array(rows, 0);
+ let sparse_nulls = create_decimal_array(rows, 10);
+ let dense_nulls = create_decimal_array(rows, 2);
+
+ bench("spark_unscaled_value: no nulls", &no_nulls);
+ bench("spark_unscaled_value: sparse nulls", &sparse_nulls);
+ bench("spark_unscaled_value: dense nulls", &dense_nulls);
+}
+
+criterion_group!(benches, criterion_benchmark);
+criterion_main!(benches);
diff --git a/native/spark-expr/src/math_funcs/internal/unscaled_value.rs
b/native/spark-expr/src/math_funcs/internal/unscaled_value.rs
index a8f2e56a9f..ca25b6d920 100644
--- a/native/spark-expr/src/math_funcs/internal/unscaled_value.rs
+++ b/native/spark-expr/src/math_funcs/internal/unscaled_value.rs
@@ -15,10 +15,8 @@
// specific language governing permissions and limitations
// under the License.
-use arrow::{
- array::{AsArray, Int64Builder},
- datatypes::Decimal128Type,
-};
+use arrow::array::AsArray;
+use arrow::datatypes::{Decimal128Type, Int64Type};
use datafusion::common::{internal_err, Result as DataFusionResult,
ScalarValue};
use datafusion::physical_plan::ColumnarValue;
use std::sync::Arc;
@@ -34,11 +32,58 @@ pub fn spark_unscaled_value(args: &[ColumnarValue]) ->
DataFusionResult<Columnar
},
ColumnarValue::Array(a) => {
let arr = a.as_primitive::<Decimal128Type>();
- let mut result = Int64Builder::new();
- for v in arr.into_iter() {
- result.append_option(v.map(|v| v as i64));
- }
- Ok(ColumnarValue::Array(Arc::new(result.finish())))
+ Ok(ColumnarValue::Array(Arc::new(
+ arr.unary::<_, Int64Type>(|v| v as i64),
+ )))
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use arrow::array::{Array, Decimal128Array, Int64Array};
+
+ #[test]
+ fn test_unscaled_value_array_with_nulls() -> DataFusionResult<()> {
+ let arr: Decimal128Array =
+ vec![Some(12345), None, Some(-678), None, Some(i64::MAX as i128)]
+ .into_iter()
+ .collect::<Decimal128Array>()
+ .with_precision_and_scale(20, 2)
+ .unwrap();
+ let args = vec![ColumnarValue::Array(Arc::new(arr))];
+ let result = spark_unscaled_value(&args)?;
+
+ let ColumnarValue::Array(result) = result else {
+ panic!("Expected array result");
+ };
+ let result = result.as_primitive::<arrow::datatypes::Int64Type>();
+ let expected = Int64Array::from(vec![Some(12345), None, Some(-678),
None, Some(i64::MAX)]);
+ assert_eq!(result, &expected);
+ assert_eq!(result.null_count(), 2);
+ Ok(())
+ }
+
+ #[test]
+ fn test_unscaled_value_scalar() -> DataFusionResult<()> {
+ let args = vec![ColumnarValue::Scalar(ScalarValue::Decimal128(
+ Some(12345),
+ 20,
+ 2,
+ ))];
+ let result = spark_unscaled_value(&args)?;
+ assert!(matches!(
+ result,
+ ColumnarValue::Scalar(ScalarValue::Int64(Some(12345)))
+ ));
+
+ let args = vec![ColumnarValue::Scalar(ScalarValue::Decimal128(None,
20, 2))];
+ let result = spark_unscaled_value(&args)?;
+ assert!(matches!(
+ result,
+ ColumnarValue::Scalar(ScalarValue::Int64(None))
+ ));
+ Ok(())
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]