From 0a76da8b3d88dd1b88e0fb5de68d3aaab5ffd872 Mon Sep 17 00:00:00 2001 From: Runji Wang Date: Fri, 9 Dec 2022 12:54:06 +0800 Subject: [PATCH 1/7] add bench for expr Signed-off-by: Runji Wang --- Cargo.lock | 1 + src/expr/Cargo.toml | 7 +++++ src/expr/benches/expr.rs | 68 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 src/expr/benches/expr.rs diff --git a/Cargo.lock b/Cargo.lock index 48afff670a710..99538e5334cdf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5427,6 +5427,7 @@ dependencies = [ "chrono", "chrono-tz", "crc32fast", + "criterion", "dyn-clone", "either", "hex", diff --git a/src/expr/Cargo.toml b/src/expr/Cargo.toml index 328d41de80ab1..4f3a04349ce2e 100644 --- a/src/expr/Cargo.toml +++ b/src/expr/Cargo.toml @@ -43,3 +43,10 @@ tonic = { version = "0.2", package = "madsim-tonic" } [target.'cfg(not(madsim))'.dependencies] workspace-hack = { path = "../workspace-hack" } + +[dev-dependencies] +criterion = "0.4" + +[[bench]] +name = "expr" +harness = false diff --git a/src/expr/benches/expr.rs b/src/expr/benches/expr.rs new file mode 100644 index 0000000000000..e5d1429708651 --- /dev/null +++ b/src/expr/benches/expr.rs @@ -0,0 +1,68 @@ +// Copyright 2022 Singularity Data +// +// Licensed 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 criterion::{criterion_group, criterion_main, Criterion}; +use risingwave_common::array::{DataChunk, I32Array}; +use risingwave_common::types::DataType; +use risingwave_expr::expr::*; +use risingwave_pb::expr::expr_node::Type; + +criterion_group!(benches, bench_expr, bench_raw); +criterion_main!(benches); + +fn bench_expr(c: &mut Criterion) { + let input = DataChunk::new( + vec![ + I32Array::from_iter(0..1024).into(), + I32Array::from_iter(0..1024).into(), + ], + 1024, + ); + + let add = new_binary_expr( + Type::Add, + DataType::Int32, + InputRefExpression::new(DataType::Int32, 0).boxed(), + InputRefExpression::new(DataType::Int32, 1).boxed(), + ) + .unwrap(); + c.bench_function("add/i32", |bencher| { + bencher.iter(|| add.eval(&input).unwrap()) + }); + + let mul = new_binary_expr( + Type::Multiply, + DataType::Int32, + InputRefExpression::new(DataType::Int32, 0).boxed(), + InputRefExpression::new(DataType::Int32, 1).boxed(), + ) + .unwrap(); + c.bench_function("mul/i32", |bencher| { + bencher.iter(|| mul.eval(&input).unwrap()) + }); +} + +/// Evaluate on raw Rust array. +fn bench_raw(c: &mut Criterion) { + let a = (0..1024).collect::>(); + let b = (0..1024).collect::>(); + c.bench_function("raw/add/i32", |bencher| { + bencher.iter(|| { + a.iter() + .zip(b.iter()) + .map(|(a, b)| a + b) + .collect::>() + }) + }); +} From 92b92711d2c379ad424f6b5e2b0d84ae2dfdb9a5 Mon Sep 17 00:00:00 2001 From: Runji Wang Date: Fri, 9 Dec 2022 16:48:35 +0800 Subject: [PATCH 2/7] add more baselines Signed-off-by: Runji Wang --- src/expr/benches/expr.rs | 94 +++++++++++++++++++++++++++++++--------- 1 file changed, 73 insertions(+), 21 deletions(-) diff --git a/src/expr/benches/expr.rs b/src/expr/benches/expr.rs index e5d1429708651..d79530de1e2b7 100644 --- a/src/expr/benches/expr.rs +++ b/src/expr/benches/expr.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![feature(iterator_try_collect)] + use criterion::{criterion_group, criterion_main, Criterion}; use risingwave_common::array::{DataChunk, I32Array}; use risingwave_common::types::DataType; @@ -21,43 +23,41 @@ use risingwave_pb::expr::expr_node::Type; criterion_group!(benches, bench_expr, bench_raw); criterion_main!(benches); +const CHUNK_SIZE: usize = 1024; + fn bench_expr(c: &mut Criterion) { let input = DataChunk::new( vec![ - I32Array::from_iter(0..1024).into(), - I32Array::from_iter(0..1024).into(), + I32Array::from_iter(0..CHUNK_SIZE as i32).into(), + I32Array::from_iter(0..CHUNK_SIZE as i32).into(), ], 1024, ); - let add = new_binary_expr( - Type::Add, - DataType::Int32, - InputRefExpression::new(DataType::Int32, 0).boxed(), - InputRefExpression::new(DataType::Int32, 1).boxed(), - ) - .unwrap(); - c.bench_function("add/i32", |bencher| { + let i0 = || InputRefExpression::new(DataType::Int32, 0).boxed(); + let i1 = || InputRefExpression::new(DataType::Int32, 0).boxed(); + c.bench_function("expr/inputref", |bencher| { + let inputref = i0(); + bencher.iter(|| inputref.eval(&input).unwrap()) + }); + c.bench_function("expr/add/i32", |bencher| { + let add = new_binary_expr(Type::Add, DataType::Int32, i0(), i1()).unwrap(); bencher.iter(|| add.eval(&input).unwrap()) }); - - let mul = new_binary_expr( - Type::Multiply, - DataType::Int32, - InputRefExpression::new(DataType::Int32, 0).boxed(), - InputRefExpression::new(DataType::Int32, 1).boxed(), - ) - .unwrap(); - c.bench_function("mul/i32", |bencher| { + c.bench_function("expr/mul/i32", |bencher| { + let mul = new_binary_expr(Type::Multiply, DataType::Int32, i0(), i1()).unwrap(); bencher.iter(|| mul.eval(&input).unwrap()) }); } /// Evaluate on raw Rust array. +/// +/// This could be used as a baseline to compare and tune our expressions. fn bench_raw(c: &mut Criterion) { - let a = (0..1024).collect::>(); - let b = (0..1024).collect::>(); + // ~80ns c.bench_function("raw/add/i32", |bencher| { + let a = (0..CHUNK_SIZE as i32).collect::>(); + let b = (0..CHUNK_SIZE as i32).collect::>(); bencher.iter(|| { a.iter() .zip(b.iter()) @@ -65,4 +65,56 @@ fn bench_raw(c: &mut Criterion) { .collect::>() }) }); + // ~200ns + c.bench_function("raw/add/Option", |bencher| { + let a = (0..CHUNK_SIZE as i32).map(Some).collect::>(); + let b = (0..CHUNK_SIZE as i32).map(Some).collect::>(); + bencher.iter(|| { + a.iter() + .zip(b.iter()) + .map(|(a, b)| match (a, b) { + (Some(a), Some(b)) => Some(a + b), + _ => None, + }) + .collect::>() + }) + }); + // ~1300ns + c.bench_function("raw/add/Option (checked)", |bencher| { + let a = (0..CHUNK_SIZE as i32).map(Some).collect::>(); + let b = (0..CHUNK_SIZE as i32).map(Some).collect::>(); + struct Overflow; + bencher.iter(|| { + a.iter() + .zip(b.iter()) + .map(|(a, b)| match (a, b) { + (Some(a), Some(b)) => Some(a.checked_add(*b).ok_or(Overflow)), + _ => None, + }) + .try_collect::>() + }) + }); + // ~2600ns + c.bench_function("raw/add/Option (cast,checked)", |bencher| { + let a = (0..CHUNK_SIZE as i32).map(Some).collect::>(); + let b = (0..CHUNK_SIZE as i32).map(Some).collect::>(); + enum Error { + Overflow, + Cast, + } + fn checked_add(a: i32, b: i32) -> Result { + let a: i32 = a.try_into().map_err(|_| Error::Cast)?; + let b: i32 = b.try_into().map_err(|_| Error::Cast)?; + a.checked_add(b).ok_or(Error::Overflow) + } + bencher.iter(|| { + a.iter() + .zip(b.iter()) + .map(|(a, b)| match (a, b) { + (Some(a), Some(b)) => Some(checked_add(*a, *b)), + _ => None, + }) + .try_collect::>() + }) + }); } From 35b29cf853514526fbb59323c0c0f3fbcb8fb957 Mon Sep 17 00:00:00 2001 From: Runji Wang Date: Mon, 12 Dec 2022 11:57:50 +0800 Subject: [PATCH 3/7] bench sum agg Signed-off-by: Runji Wang --- src/expr/benches/expr.rs | 22 +++++++++++++++++++++- src/expr/src/vector_op/agg/mod.rs | 2 +- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/expr/benches/expr.rs b/src/expr/benches/expr.rs index d79530de1e2b7..7d76336a4f268 100644 --- a/src/expr/benches/expr.rs +++ b/src/expr/benches/expr.rs @@ -18,6 +18,7 @@ use criterion::{criterion_group, criterion_main, Criterion}; use risingwave_common::array::{DataChunk, I32Array}; use risingwave_common::types::DataType; use risingwave_expr::expr::*; +use risingwave_expr::vector_op::agg::create_agg_state_unary; use risingwave_pb::expr::expr_node::Type; criterion_group!(benches, bench_expr, bench_raw); @@ -35,11 +36,15 @@ fn bench_expr(c: &mut Criterion) { ); let i0 = || InputRefExpression::new(DataType::Int32, 0).boxed(); - let i1 = || InputRefExpression::new(DataType::Int32, 0).boxed(); + let i1 = || InputRefExpression::new(DataType::Int32, 1).boxed(); c.bench_function("expr/inputref", |bencher| { let inputref = i0(); bencher.iter(|| inputref.eval(&input).unwrap()) }); + c.bench_function("expr/constant", |bencher| { + let constant = LiteralExpression::new(DataType::Int32, Some(1_i32.into())); + bencher.iter(|| constant.eval(&input).unwrap()) + }); c.bench_function("expr/add/i32", |bencher| { let add = new_binary_expr(Type::Add, DataType::Int32, i0(), i1()).unwrap(); bencher.iter(|| add.eval(&input).unwrap()) @@ -48,12 +53,27 @@ fn bench_expr(c: &mut Criterion) { let mul = new_binary_expr(Type::Multiply, DataType::Int32, i0(), i1()).unwrap(); bencher.iter(|| mul.eval(&input).unwrap()) }); + c.bench_function("expr/eq/i32", |bencher| { + let mul = new_binary_expr(Type::Equal, DataType::Int32, i0(), i1()).unwrap(); + bencher.iter(|| mul.eval(&input).unwrap()) + }); + c.bench_function("expr/sum/i32", |bencher| { + let mut sum = + create_agg_state_unary(DataType::Int32, 0, AggKind::Sum, DataType::Int64, false) + .unwrap(); + bencher.iter(|| sum.update_multi(&input, 0, 1024).unwrap()) + }); } /// Evaluate on raw Rust array. /// /// This could be used as a baseline to compare and tune our expressions. fn bench_raw(c: &mut Criterion) { + // ~55ns + c.bench_function("raw/sum/i32", |bencher| { + let a = (0..CHUNK_SIZE as i32).collect::>(); + bencher.iter(|| a.iter().sum::()) + }); // ~80ns c.bench_function("raw/add/i32", |bencher| { let a = (0..CHUNK_SIZE as i32).collect::>(); diff --git a/src/expr/src/vector_op/agg/mod.rs b/src/expr/src/vector_op/agg/mod.rs index e392bf187bc69..58e26b066f33a 100644 --- a/src/expr/src/vector_op/agg/mod.rs +++ b/src/expr/src/vector_op/agg/mod.rs @@ -23,5 +23,5 @@ mod general_distinct_agg; mod general_sorted_grouper; mod string_agg; -pub use aggregator::{AggStateFactory, BoxedAggState}; +pub use aggregator::{create_agg_state_unary, AggStateFactory, BoxedAggState}; pub use general_sorted_grouper::{create_sorted_grouper, BoxedSortedGrouper, EqGroups}; From e39ee8d0a40e8779b3eb8eff0453b180d5e8d9e3 Mon Sep 17 00:00:00 2001 From: Runji Wang Date: Mon, 12 Dec 2022 14:17:51 +0800 Subject: [PATCH 4/7] fix error after rebase Signed-off-by: Runji Wang --- src/expr/benches/expr.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/expr/benches/expr.rs b/src/expr/benches/expr.rs index 7d76336a4f268..14eca708dcf34 100644 --- a/src/expr/benches/expr.rs +++ b/src/expr/benches/expr.rs @@ -17,6 +17,7 @@ use criterion::{criterion_group, criterion_main, Criterion}; use risingwave_common::array::{DataChunk, I32Array}; use risingwave_common::types::DataType; +use risingwave_expr::expr::expr_binary_nonnull::*; use risingwave_expr::expr::*; use risingwave_expr::vector_op::agg::create_agg_state_unary; use risingwave_pb::expr::expr_node::Type; From d212a60201f7d30544a56531b7023276985e43a4 Mon Sep 17 00:00:00 2001 From: Runji Wang Date: Mon, 12 Dec 2022 15:33:44 +0800 Subject: [PATCH 5/7] fix clippy and add bench for `zip_eq` Signed-off-by: Runji Wang --- src/expr/benches/expr.rs | 49 +++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/src/expr/benches/expr.rs b/src/expr/benches/expr.rs index 14eca708dcf34..70b113630efc0 100644 --- a/src/expr/benches/expr.rs +++ b/src/expr/benches/expr.rs @@ -12,7 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![feature(iterator_try_collect)] +// std try_collect is slower than itertools +// #![feature(iterator_try_collect)] + +// allow using `zip`. +// `zip_eq` is a source of poor performance. +#![allow(clippy::disallowed_methods)] use criterion::{criterion_group, criterion_main, Criterion}; use risingwave_common::array::{DataChunk, I32Array}; @@ -75,7 +80,7 @@ fn bench_raw(c: &mut Criterion) { let a = (0..CHUNK_SIZE as i32).collect::>(); bencher.iter(|| a.iter().sum::()) }); - // ~80ns + // ~90ns c.bench_function("raw/add/i32", |bencher| { let a = (0..CHUNK_SIZE as i32).collect::>(); let b = (0..CHUNK_SIZE as i32).collect::>(); @@ -86,13 +91,22 @@ fn bench_raw(c: &mut Criterion) { .collect::>() }) }); - // ~200ns - c.bench_function("raw/add/Option", |bencher| { + // ~600ns + c.bench_function("raw/add/i32/zip_eq", |bencher| { + let a = (0..CHUNK_SIZE as i32).collect::>(); + let b = (0..CHUNK_SIZE as i32).collect::>(); + bencher.iter(|| { + itertools::Itertools::zip_eq(a.iter(), b.iter()) + .map(|(a, b)| a + b) + .collect::>() + }) + }); + // ~950ns + c.bench_function("raw/add/Option/zip_eq", |bencher| { let a = (0..CHUNK_SIZE as i32).map(Some).collect::>(); let b = (0..CHUNK_SIZE as i32).map(Some).collect::>(); bencher.iter(|| { - a.iter() - .zip(b.iter()) + itertools::Itertools::zip_eq(a.iter(), b.iter()) .map(|(a, b)| match (a, b) { (Some(a), Some(b)) => Some(a + b), _ => None, @@ -100,42 +114,45 @@ fn bench_raw(c: &mut Criterion) { .collect::>() }) }); - // ~1300ns - c.bench_function("raw/add/Option (checked)", |bencher| { + // ~2100ns + c.bench_function("raw/add/Option/zip_eq,checked", |bencher| { let a = (0..CHUNK_SIZE as i32).map(Some).collect::>(); let b = (0..CHUNK_SIZE as i32).map(Some).collect::>(); struct Overflow; bencher.iter(|| { + use itertools::Itertools; a.iter() .zip(b.iter()) .map(|(a, b)| match (a, b) { - (Some(a), Some(b)) => Some(a.checked_add(*b).ok_or(Overflow)), - _ => None, + (Some(a), Some(b)) => a.checked_add(*b).ok_or(Overflow).map(Some), + _ => Ok(None), }) - .try_collect::>() + .try_collect::<_, Vec<_>, Overflow>() }) }); - // ~2600ns - c.bench_function("raw/add/Option (cast,checked)", |bencher| { + // ~2400ns + c.bench_function("raw/add/Option/zip_eq,checked,cast", |bencher| { let a = (0..CHUNK_SIZE as i32).map(Some).collect::>(); let b = (0..CHUNK_SIZE as i32).map(Some).collect::>(); enum Error { Overflow, Cast, } + #[allow(clippy::useless_conversion)] fn checked_add(a: i32, b: i32) -> Result { let a: i32 = a.try_into().map_err(|_| Error::Cast)?; let b: i32 = b.try_into().map_err(|_| Error::Cast)?; a.checked_add(b).ok_or(Error::Overflow) } bencher.iter(|| { + use itertools::Itertools; a.iter() .zip(b.iter()) .map(|(a, b)| match (a, b) { - (Some(a), Some(b)) => Some(checked_add(*a, *b)), - _ => None, + (Some(a), Some(b)) => checked_add(*a, *b).map(Some), + _ => Ok(None), }) - .try_collect::>() + .try_collect::<_, Vec<_>, Error>() }) }); } From 980dcdae99f3e3461f3922c94e08113fa1fa2b6c Mon Sep 17 00:00:00 2001 From: Runji Wang Date: Mon, 12 Dec 2022 16:22:37 +0800 Subject: [PATCH 6/7] add bench for collecting to array Signed-off-by: Runji Wang --- src/expr/benches/expr.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/expr/benches/expr.rs b/src/expr/benches/expr.rs index 70b113630efc0..232a5f8c892f4 100644 --- a/src/expr/benches/expr.rs +++ b/src/expr/benches/expr.rs @@ -155,4 +155,32 @@ fn bench_raw(c: &mut Criterion) { .try_collect::<_, Vec<_>, Error>() }) }); + // ~3100ns + c.bench_function( + "raw/add/Option/zip_eq,checked,cast,collect_array", + |bencher| { + let a = (0..CHUNK_SIZE as i32).map(Some).collect::>(); + let b = (0..CHUNK_SIZE as i32).map(Some).collect::>(); + enum Error { + Overflow, + Cast, + } + #[allow(clippy::useless_conversion)] + fn checked_add(a: i32, b: i32) -> Result { + let a: i32 = a.try_into().map_err(|_| Error::Cast)?; + let b: i32 = b.try_into().map_err(|_| Error::Cast)?; + a.checked_add(b).ok_or(Error::Overflow) + } + bencher.iter(|| { + use itertools::Itertools; + a.iter() + .zip(b.iter()) + .map(|(a, b)| match (a, b) { + (Some(a), Some(b)) => checked_add(*a, *b).map(Some), + _ => Ok(None), + }) + .try_collect::<_, I32Array, Error>() + }) + }, + ); } From 03a12bd138f3b006cf721ec702dd872e22025bc8 Mon Sep 17 00:00:00 2001 From: Runji Wang Date: Mon, 12 Dec 2022 16:35:56 +0800 Subject: [PATCH 7/7] fix zip_eq Signed-off-by: Runji Wang --- src/expr/benches/expr.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/expr/benches/expr.rs b/src/expr/benches/expr.rs index 232a5f8c892f4..f177c962ad469 100644 --- a/src/expr/benches/expr.rs +++ b/src/expr/benches/expr.rs @@ -121,8 +121,7 @@ fn bench_raw(c: &mut Criterion) { struct Overflow; bencher.iter(|| { use itertools::Itertools; - a.iter() - .zip(b.iter()) + itertools::Itertools::zip_eq(a.iter(), b.iter()) .map(|(a, b)| match (a, b) { (Some(a), Some(b)) => a.checked_add(*b).ok_or(Overflow).map(Some), _ => Ok(None), @@ -146,8 +145,7 @@ fn bench_raw(c: &mut Criterion) { } bencher.iter(|| { use itertools::Itertools; - a.iter() - .zip(b.iter()) + itertools::Itertools::zip_eq(a.iter(), b.iter()) .map(|(a, b)| match (a, b) { (Some(a), Some(b)) => checked_add(*a, *b).map(Some), _ => Ok(None), @@ -173,8 +171,7 @@ fn bench_raw(c: &mut Criterion) { } bencher.iter(|| { use itertools::Itertools; - a.iter() - .zip(b.iter()) + itertools::Itertools::zip_eq(a.iter(), b.iter()) .map(|(a, b)| match (a, b) { (Some(a), Some(b)) => checked_add(*a, *b).map(Some), _ => Ok(None),