Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 4.2k
ARROW-9937: [Rust] [DataFusion] Improved aggregations#8172
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
48104401ab90b588378d057f9e0d19ec08a440abf8c04ca46b64e2009c9056919ad70b4a66466File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -19,18 +19,26 @@ | ||
| extern crate criterion; | ||
| use criterion::Criterion; | ||
| use std::env; | ||
| use rand::seq::SliceRandom; | ||
| use rand::Rng; | ||
| use std::sync::Arc; | ||
| extern crate arrow; | ||
| extern crate datafusion; | ||
| use arrow::datatypes::{DataType, Field, Schema}; | ||
| use arrow::{ | ||
| array::Float32Array, | ||
| array::Float64Array, | ||
| array::StringArray, | ||
| datatypes::{DataType, Field, Schema}, | ||
| record_batch::RecordBatch, | ||
| }; | ||
| use datafusion::datasource::{CsvFile, CsvReadOptions, MemTable}; | ||
| use datafusion::datasource::MemTable; | ||
| use datafusion::error::Result; | ||
| use datafusion::execution::context::ExecutionContext; | ||
| fn aggregate_query(ctx: &mut ExecutionContext, sql: &str) { | ||
| fn query(ctx: &mut ExecutionContext, sql: &str) { | ||
| // execute the query | ||
| let df = ctx.sql(&sql).unwrap(); | ||
| let results = df.collect().unwrap(); | ||
| @@ -39,72 +47,107 @@ fn aggregate_query(ctx: &mut ExecutionContext, sql: &str) { | ||
| for _batch in results {} | ||
| } | ||
| fn create_context() -> ExecutionContext { | ||
| // define schema for data source (csv file) | ||
| fn create_data(size: usize, null_density: f64) -> Vec<Option<f64>> { | ||
| // use random numbers to avoid spurious compiler optimizations wrt to branching | ||
| let mut rng = rand::thread_rng(); | ||
| (0..size) | ||
| .map(|_| { | ||
| if rng.gen::<f64>() > null_density { | ||
| None | ||
| } else { | ||
| Some(rng.gen::<f64>()) | ||
| } | ||
| }) | ||
| .collect() | ||
| } | ||
| fn create_context( | ||
| partitions_len: usize, | ||
| array_len: usize, | ||
| batch_size: usize, | ||
| ) -> Result<ExecutionContext> { | ||
| // define a schema. | ||
| let schema = Arc::new(Schema::new(vec![ | ||
| Field::new("c1", DataType::Utf8, false), | ||
| Field::new("c2", DataType::UInt32, false), | ||
| Field::new("c3", DataType::Int8, false), | ||
| Field::new("c4", DataType::Int16, false), | ||
| Field::new("c5", DataType::Int32, false), | ||
| Field::new("c6", DataType::Int64, false), | ||
| Field::new("c7", DataType::UInt8, false), | ||
| Field::new("c8", DataType::UInt16, false), | ||
| Field::new("c9", DataType::UInt32, false), | ||
| Field::new("c10", DataType::UInt64, false), | ||
| Field::new("c11", DataType::Float32, false), | ||
| Field::new("c12", DataType::Float64, false), | ||
| Field::new("c13", DataType::Utf8, false), | ||
| Field::new("utf8", DataType::Utf8, false), | ||
| Field::new("f32", DataType::Float32, false), | ||
| Field::new("f64", DataType::Float64, false), | ||
| ])); | ||
| let testdata = env::var("ARROW_TEST_DATA").expect("ARROW_TEST_DATA not defined"); | ||
| // define data. | ||
| let partitions = (0..partitions_len) | ||
| .map(|_| { | ||
| (0..array_len / batch_size / partitions_len) | ||
| .map(|i| { | ||
| // the 4 here is the number of different keys. | ||
| // a higher number increase sparseness | ||
| let vs = vec![0, 1, 2, 3]; | ||
| let keys: Vec<String> = (0..batch_size) | ||
| .map( | ||
| // use random numbers to avoid spurious compiler optimizations wrt to branching | ||
| |_| format!("hi{:?}", vs.choose(&mut rand::thread_rng())), | ||
| ) | ||
| .collect(); | ||
| let keys: Vec<&str> = keys.iter().map(|e| &**e).collect(); | ||
| let values = create_data(batch_size, 0.5); | ||
| RecordBatch::try_new( | ||
| schema.clone(), | ||
| vec![ | ||
| Arc::new(StringArray::from(keys)), | ||
| Arc::new(Float32Array::from(vec![i as f32; batch_size])), | ||
| Arc::new(Float64Array::from(values)), | ||
| ], | ||
| ) | ||
| .unwrap() | ||
| }) | ||
| .collect::<Vec<_>>() | ||
| }) | ||
| .collect::<Vec<_>>(); | ||
| // create CSV data source | ||
| let csv = CsvFile::try_new( | ||
| &format!("{}/csv/aggregate_test_100.csv", testdata), | ||
| CsvReadOptions::new().schema(&schema), | ||
| ) | ||
| .unwrap(); | ||
| let mut ctx = ExecutionContext::new(); | ||
| let mem_table = MemTable::load(&csv).unwrap(); | ||
| // declare a table in memory. In spark API, this corresponds to createDataFrame(...). | ||
| let provider = MemTable::new(schema, partitions)?; | ||
| ctx.register_table("t", Box::new(provider)); | ||
| // create local execution context | ||
| let mut ctx = ExecutionContext::new(); | ||
| ctx.register_table("aggregate_test_100", Box::new(mem_table)); | ||
| ctx | ||
| Ok(ctx) | ||
| } | ||
| fn criterion_benchmark(c: &mut Criterion) { | ||
| c.bench_function("aggregate_query_no_group_by", |b| { | ||
| let mut ctx = create_context(); | ||
| let partitions_len = 4; | ||
| let array_len = 32768; // 2^15 | ||
| let batch_size = 2048; // 2^11 | ||
| let mut ctx = create_context(partitions_len, array_len, batch_size).unwrap(); | ||
| c.bench_function("aggregate_query_no_group_by 15 12", |b| { | ||
jorgecarleitao marked this conversation as resolved.
Outdated
Uh oh!There was an error while loading. Please reload this page. | ||
| b.iter(|| { | ||
| aggregate_query( | ||
| query( | ||
| &mut ctx, | ||
| "SELECT MIN(c12), MAX(c12) \ | ||
| FROM aggregate_test_100", | ||
| "SELECT MIN(f64), AVG(f64), COUNT(f64) \ | ||
| FROM t", | ||
| ) | ||
| }) | ||
| }); | ||
| c.bench_function("aggregate_query_group_by", |b| { | ||
| let mut ctx = create_context(); | ||
| c.bench_function("aggregate_query_group_by 15 12", |b| { | ||
| b.iter(|| { | ||
| aggregate_query( | ||
| query( | ||
| &mut ctx, | ||
| "SELECT c1, MIN(c12), MAX(c12) \ | ||
| FROM aggregate_test_100 GROUP BY c1", | ||
| "SELECT utf8, MIN(f64), AVG(f64), COUNT(f64) \ | ||
| FROM t GROUP BY utf8", | ||
| ) | ||
| }) | ||
| }); | ||
| c.bench_function("aggregate_query_group_by_with_filter", |b| { | ||
| let mut ctx = create_context(); | ||
| c.bench_function("aggregate_query_group_by_with_filter 15 12", |b| { | ||
| b.iter(|| { | ||
| aggregate_query( | ||
| query( | ||
| &mut ctx, | ||
| "SELECT c1, MIN(c12), MAX(c12) \ | ||
| FROM aggregate_test_100 \ | ||
| WHERE c11 > 0.1 AND c11 < 0.9 GROUP BY c1", | ||
| "SELECT utf8, MIN(f64), AVG(f64), COUNT(f64) \ | ||
| FROM t \ | ||
| WHERE f32 > 10 AND f32 < 20 GROUP BY utf8", | ||
| ) | ||
| }) | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -516,7 +516,8 @@ mod tests { | ||
| use crate::test; | ||
| use crate::variable::VarType; | ||
| use arrow::array::{ | ||
| ArrayRef, Int32Array, PrimitiveArrayOps, StringArray, StringArrayOps, | ||
| ArrayRef, Float64Array, Int32Array, PrimitiveArrayOps, StringArray, | ||
| StringArrayOps, | ||
| }; | ||
| use arrow::compute::add; | ||
| use std::fs::File; | ||
| @@ -847,6 +848,24 @@ mod tests { | ||
| Ok(()) | ||
| } | ||
| #[test] | ||
| fn aggregate_grouped_empty() -> Result<()> { | ||
MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A test for grouped with an empty result | ||
| let results = | ||
| execute("SELECT c1, AVG(c2) FROM test WHERE c1 = 123 GROUP BY c1", 4)?; | ||
| assert_eq!(results.len(), 1); | ||
| let batch = &results[0]; | ||
| assert_eq!(field_names(batch), vec!["c1", "AVG(c2)"]); | ||
| let expected: Vec<&str> = vec![]; | ||
| let mut rows = test::format_batch(&batch); | ||
| rows.sort(); | ||
| assert_eq!(rows, expected); | ||
| Ok(()) | ||
| } | ||
| #[test] | ||
| fn aggregate_grouped_max() -> Result<()> { | ||
| let results = execute("SELECT c1, MAX(c2) FROM test GROUP BY c1", 4)?; | ||
| @@ -1146,6 +1165,41 @@ mod tests { | ||
| Ok(()) | ||
| } | ||
| #[test] | ||
| fn simple_avg() -> Result<()> { | ||
| let schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]); | ||
| let batch1 = RecordBatch::try_new( | ||
| Arc::new(schema.clone()), | ||
| vec![Arc::new(Int32Array::from(vec![1, 2, 3]))], | ||
| )?; | ||
| let batch2 = RecordBatch::try_new( | ||
| Arc::new(schema.clone()), | ||
| vec![Arc::new(Int32Array::from(vec![4, 5]))], | ||
| )?; | ||
| let mut ctx = ExecutionContext::new(); | ||
| let provider = MemTable::new(Arc::new(schema), vec![vec![batch1], vec![batch2]])?; | ||
| ctx.register_table("t", Box::new(provider)); | ||
| let result = collect(&mut ctx, "SELECT AVG(a) FROM t")?; | ||
| let batch = &result[0]; | ||
| assert_eq!(1, batch.num_columns()); | ||
| assert_eq!(1, batch.num_rows()); | ||
| let values = batch | ||
| .column(0) | ||
| .as_any() | ||
| .downcast_ref::<Float64Array>() | ||
| .expect("failed to cast version"); | ||
| assert_eq!(values.len(), 1); | ||
| // avg(1,2,3,4,5) = 3.0 | ||
| assert_eq!(values.value(0), 3.0_f64); | ||
MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test fails in master, with 3.25 != 3.0 | ||
| Ok(()) | ||
| } | ||
| #[test] | ||
| fn custom_query_planner() -> Result<()> { | ||
| let mut ctx = ExecutionContext::with_config( | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so the idea here is that the code is faster if we don't have to check each element for nulls?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes. Generally, operations on non-null fields are faster because there isn't an unpredictable branch on the loop. This check removes that unpredictable branch altogether when there are no nulls.
We could probably still get some more juice by using some of the vertical operations supported by packed_simd.