Skip to content

Commit 373522a

Browse files
chore[bench-vortex]: unify the different benchmark binaries (#3785)
Signed-off-by: Joe Isaacs <[email protected]> --------- Signed-off-by: Joe Isaacs <[email protected]>
1 parent 9d1b837 commit 373522a

23 files changed

+1242
-1116
lines changed

.github/workflows/generate-benchmarks-s3.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ jobs:
3737
shell: bash
3838
run: |
3939
# We run each query once to make sure we don't upload a file if there's a bug that causes a panic.
40-
cargo run --release --bin clickbench --package bench-vortex -- --targets datafusion:parquet,datafusion:vortex -i1
40+
cargo run --release --bin query_bench --package bench-vortex -- clickbench --targets datafusion:parquet,datafusion:vortex -i1
4141
aws s3 rm --recursive s3://vortex-bench-dev-eu/develop/clickbench/
4242
aws s3 cp --recursive bench-vortex/data/clickbench_partitioned s3://vortex-bench-dev-eu/develop/clickbench/
4343
rm -rf bench-vortex/data/clickbench_partitioned/

.github/workflows/sql-benchmarks.yml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,17 @@ jobs:
2929
# https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/running-variations-of-jobs-in-a-workflow#example-adding-configurations
3030
include:
3131
- id: tpch-nvme
32-
binary_name: tpch
32+
subcommand: tpch
3333
name: TPC-H on NVME
3434
targets: "datafusion:arrow,datafusion:parquet,datafusion:vortex,duckdb:parquet,duckdb:vortex,duckdb:duckdb"
3535
scale_factor: "--scale-factor ${{inputs.scale_factor}}"
3636
- id: clickbench-nvme
37-
binary_name: clickbench
37+
subcommand: clickbench
3838
name: Clickbench on NVME
3939
targets: "datafusion:parquet,datafusion:vortex,duckdb:parquet,duckdb:vortex,duckdb:duckdb"
4040
scale_factor: ""
4141
- id: tpch-s3
42-
binary_name: tpch
42+
subcommand: tpch
4343
name: TPC-H on S3
4444
local_dir: bench-vortex/data/tpch/${{inputs.scale_factor}}
4545
remote_storage: s3://vortex-bench-dev-eu/${{github.ref_name}}/tpch/${{inputs.scale_factor}}/
@@ -79,7 +79,7 @@ jobs:
7979
env:
8080
RUSTFLAGS: '-C target-cpu=native -C force-frame-pointers=yes'
8181
run: |
82-
cargo build --bin ${{ matrix.binary_name }} --package bench-vortex --profile release_debug
82+
cargo build --bin query_bench --package bench-vortex --profile release_debug
8383
8484
- name: Generate data
8585
shell: bash
@@ -88,11 +88,11 @@ jobs:
8888
run: |
8989
# Generate data, running each query once to make sure they don't panic.
9090
echo "datafusion:parquet"
91-
target/release_debug/${{ matrix.binary_name }} --targets datafusion:parquet -i1 -d gh-json ${{ matrix.scale_factor }}
91+
target/release_debug/query_bench ${{ matrix.subcommand }} --targets datafusion:parquet -i1 -d gh-json ${{ matrix.scale_factor }}
9292
echo "datafusion:vortex"
93-
target/release_debug/${{ matrix.binary_name }} --targets datafusion:vortex -i1 -d gh-json ${{ matrix.scale_factor }}
93+
target/release_debug/query_bench ${{ matrix.subcommand }} --targets datafusion:vortex -i1 -d gh-json ${{ matrix.scale_factor }}
9494
echo "duckdb:vortex"
95-
target/release_debug/${{ matrix.binary_name }} --targets duckdb:vortex -i1 -d gh-json ${{ matrix.scale_factor }}
95+
target/release_debug/query_bench ${{ matrix.subcommand }} --targets duckdb:vortex -i1 -d gh-json ${{ matrix.scale_factor }}
9696
9797
- name: Setup AWS CLI
9898
uses: aws-actions/configure-aws-credentials@v4
@@ -126,7 +126,7 @@ jobs:
126126
OTEL_EXPORTER_OTLP_HEADERS: '${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}'
127127
OTEL_RESOURCE_ATTRIBUTES: 'bench-name=${{ matrix.id }}'
128128
run: |
129-
target/release_debug/${{ matrix.binary_name }} \
129+
target/release_debug/query_bench ${{ matrix.subcommand }} \
130130
-d gh-json \
131131
--targets ${{ matrix.targets }} \
132132
--export-spans \
@@ -144,7 +144,7 @@ jobs:
144144
OTEL_EXPORTER_OTLP_HEADERS: '${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}'
145145
OTEL_RESOURCE_ATTRIBUTES: 'bench-name=${{ matrix.id }}'
146146
run: |
147-
target/release_debug/${{ matrix.binary_name }} \
147+
target/release_debug/query_bench ${{ matrix.subcommand }} \
148148
--use-remote-data-dir ${{ matrix.remote_storage }} \
149149
--targets ${{ matrix.targets }} \
150150
--export-spans \

bench-vortex/README.md

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,27 @@ comparing vortex compression to parquet and debugging vortex compression perform
1111
This binary compresses a file using vortex compression and writes the compressed file to disk where it can be examined
1212
or used for other operations.
1313

14-
### `comparison.rs`
1514

16-
This binary compresses a dataset using vortex compression and parquet, taking some stats on the compression performance
17-
of each run, and writes out these stats to a csv.
15+
### `query_bench`
1816

19-
* This csv can then be loaded into duckdb and analyzed with the included comparison.sql script.
17+
This is the unified benchmark runner that supports multiple benchmark suites including TPC-H, ClickBench, and TPC-DS.
2018

21-
### `tpch.rs`
19+
To run the TPC-H benchmarks you can use:
2220

23-
This binary will run TPC-H query 1 using DataFusion, comparing the Vortex in-memory provider against Arrow and CSV.
21+
```bash
22+
cargo run --bin query_bench -- tpch
23+
```
2424

25-
To run the tpch benchmarks you can use:
25+
To run the ClickBench benchmarks:
2626

2727
```bash
28-
cargo run --bin tpch
28+
cargo run --bin query_bench -- clickbench
2929
```
3030

31-
There are also clickbench and tpc-ds benchmarks, which can be run similarly.
32-
3331
For profiling, you can open in Instruments using the following invocation:
3432

3533
```
36-
cargo instruments -p bench-vortex --bin tpch --template Time --profile bench
34+
cargo instruments -p bench-vortex --bin query_bench --template Time --profile bench -- tpch
3735
```
3836

3937
### Data directory
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3+
4+
//! Benchmark driver that handles CLI logic and orchestrates benchmark execution
5+
6+
use std::path::PathBuf;
7+
8+
use anyhow::Result;
9+
use indicatif::ProgressBar;
10+
use itertools::Itertools;
11+
use log::warn;
12+
use vortex_datafusion::metrics::VortexMetricsFinder;
13+
14+
use crate::benchmark_trait::Benchmark;
15+
use crate::display::DisplayFormat;
16+
use crate::engines::{EngineCtx, benchmark_datafusion_query, benchmark_duckdb_query};
17+
use crate::measurements::QueryMeasurement;
18+
use crate::metrics::{MetricsSetExt, export_plan_spans};
19+
use crate::query_bench::{filter_queries, print_results, setup_logging_and_tracing};
20+
use crate::utils::{new_tokio_runtime, url_scheme_to_storage};
21+
use crate::{Engine, Format, Target, df, vortex_panic};
22+
23+
/// Configuration for the benchmark driver
24+
pub struct DriverConfig {
25+
pub targets: Vec<Target>,
26+
pub iterations: usize,
27+
pub threads: Option<usize>,
28+
pub verbose: bool,
29+
pub display_format: DisplayFormat,
30+
pub disable_datafusion_cache: bool,
31+
pub queries: Option<Vec<usize>>,
32+
pub exclude_queries: Option<Vec<usize>>,
33+
pub output_path: Option<PathBuf>,
34+
pub emit_plan: bool,
35+
pub export_spans: bool,
36+
pub show_metrics: bool,
37+
pub hide_progress_bar: bool,
38+
}
39+
40+
/// Run a benchmark using the provided implementation and configuration
41+
pub fn run_benchmark<B: Benchmark>(benchmark: B, config: DriverConfig) -> Result<()> {
42+
let _trace_guard = setup_logging_and_tracing(
43+
config.verbose,
44+
&format!("{}.trace.json", benchmark.dataset_name()),
45+
)?;
46+
47+
// Validate arguments
48+
validate_args(&config)?;
49+
50+
// Generate data for each target (idempotent)
51+
for target in &config.targets {
52+
benchmark.generate_data(target)?;
53+
}
54+
55+
let filtered_queries = filter_queries(
56+
benchmark.queries()?,
57+
config.queries.as_ref(),
58+
config.exclude_queries.as_ref(),
59+
);
60+
61+
let progress_bar = if config.hide_progress_bar {
62+
ProgressBar::hidden()
63+
} else {
64+
ProgressBar::new((filtered_queries.len() * config.targets.len()) as u64)
65+
};
66+
67+
let mut query_measurements = Vec::new();
68+
69+
for target in config.targets.iter() {
70+
let tokio_runtime = new_tokio_runtime(config.threads);
71+
72+
let mut engine_ctx = benchmark.setup_engine_context(
73+
target,
74+
config.disable_datafusion_cache,
75+
config.emit_plan,
76+
)?;
77+
78+
tokio_runtime.block_on(benchmark.register_tables(&engine_ctx, target.format()))?;
79+
80+
let bench_measurements = execute_queries(
81+
&filtered_queries,
82+
config.iterations,
83+
&tokio_runtime,
84+
target.format(),
85+
&progress_bar,
86+
&mut engine_ctx,
87+
&benchmark,
88+
)?;
89+
90+
tokio_runtime.block_on(export_metrics_if_requested(
91+
&engine_ctx,
92+
config.export_spans,
93+
))?;
94+
95+
if config.show_metrics {
96+
print_metrics(&engine_ctx);
97+
}
98+
99+
query_measurements.extend(bench_measurements);
100+
}
101+
102+
print_results(
103+
&config.display_format,
104+
query_measurements,
105+
&config.targets,
106+
&config.output_path,
107+
)
108+
}
109+
110+
fn validate_args(config: &DriverConfig) -> Result<()> {
111+
let engines = config
112+
.targets
113+
.iter()
114+
.map(|t| t.engine())
115+
.unique()
116+
.collect_vec();
117+
118+
if (config.emit_plan || config.export_spans || config.show_metrics || config.threads.is_some())
119+
&& !engines.contains(&Engine::DataFusion)
120+
{
121+
vortex_panic!(
122+
"--emit-plan, --export-spans, --show-metrics, --threads are only valid if DataFusion is used"
123+
);
124+
}
125+
Ok(())
126+
}
127+
128+
fn execute_queries<B: Benchmark>(
129+
queries: &[(usize, String)],
130+
iterations: usize,
131+
runtime: &tokio::runtime::Runtime,
132+
format: Format,
133+
progress_bar: &ProgressBar,
134+
engine_ctx: &mut EngineCtx,
135+
benchmark: &B,
136+
) -> Result<Vec<QueryMeasurement>> {
137+
let mut query_measurements = Vec::new();
138+
let expected_row_counts = benchmark.expected_row_counts();
139+
140+
for &(query_idx, ref query_string) in queries.iter() {
141+
match engine_ctx {
142+
EngineCtx::DataFusion(ctx) => {
143+
let (runs, (row_count, execution_plan)) = runtime.block_on(async {
144+
benchmark_datafusion_query(iterations, || async {
145+
let (batches, plan) = df::execute_query(&ctx.session, query_string)
146+
.await
147+
.unwrap_or_else(|err| {
148+
vortex_panic!("query: {query_idx} failed with: {err}")
149+
});
150+
let row_count: usize = batches.iter().map(|batch| batch.num_rows()).sum();
151+
(row_count, plan)
152+
})
153+
.await
154+
});
155+
156+
// Validate row count if expected counts are provided
157+
if let Some(expected_counts) = expected_row_counts {
158+
if query_idx < expected_counts.len() {
159+
assert_eq!(
160+
row_count, expected_counts[query_idx],
161+
"Row count mismatch for query {query_idx} - datafusion:{format}",
162+
);
163+
}
164+
}
165+
166+
ctx.execution_plans
167+
.push((query_idx, execution_plan.clone()));
168+
169+
if ctx.emit_plan {
170+
df::write_execution_plan(
171+
query_idx,
172+
format,
173+
benchmark.dataset_name(),
174+
execution_plan.as_ref(),
175+
);
176+
}
177+
178+
ctx.metrics.push((
179+
query_idx,
180+
format,
181+
VortexMetricsFinder::find_all(execution_plan.as_ref()),
182+
));
183+
184+
query_measurements.push(QueryMeasurement {
185+
query_idx,
186+
target: Target::new(Engine::DataFusion, format),
187+
benchmark_dataset: benchmark.dataset(),
188+
storage: url_scheme_to_storage(benchmark.data_url())?,
189+
runs,
190+
});
191+
}
192+
EngineCtx::DuckDB(ctx) => {
193+
let (runs, row_count) =
194+
benchmark_duckdb_query(query_idx, query_string, iterations, ctx);
195+
196+
// Validate row count if expected counts are provided
197+
if let Some(expected_counts) = expected_row_counts {
198+
if query_idx < expected_counts.len() {
199+
assert_eq!(
200+
row_count, expected_counts[query_idx],
201+
"Row count mismatch for query {query_idx} - duckdb:{format}",
202+
);
203+
}
204+
}
205+
206+
query_measurements.push(QueryMeasurement {
207+
query_idx,
208+
target: Target::new(Engine::DuckDB, format),
209+
benchmark_dataset: benchmark.dataset(),
210+
storage: url_scheme_to_storage(benchmark.data_url())?,
211+
runs,
212+
});
213+
}
214+
}
215+
216+
progress_bar.inc(1);
217+
}
218+
219+
Ok(query_measurements)
220+
}
221+
222+
async fn export_metrics_if_requested(engine_ctx: &EngineCtx, export_spans: bool) -> Result<()> {
223+
if let EngineCtx::DataFusion(ctx) = engine_ctx {
224+
if export_spans {
225+
if let Err(err) = export_plan_spans(Format::OnDiskVortex, &ctx.execution_plans).await {
226+
warn!("failed to export spans {err}");
227+
}
228+
}
229+
}
230+
Ok(())
231+
}
232+
233+
fn print_metrics(engine_ctx: &EngineCtx) {
234+
if let EngineCtx::DataFusion(ctx) = engine_ctx {
235+
for (query_idx, file_format, metric_sets) in &ctx.metrics {
236+
eprintln!("metrics for query={query_idx}, {file_format}:");
237+
for (scan_idx, metrics_set) in metric_sets.iter().enumerate() {
238+
eprintln!("scan[{scan_idx}]:");
239+
for metric in metrics_set
240+
.clone()
241+
.timestamps_removed()
242+
.aggregate()
243+
.sorted_for_display()
244+
.iter()
245+
{
246+
eprintln!("{metric}");
247+
}
248+
}
249+
}
250+
}
251+
}

0 commit comments

Comments
 (0)