Skip to content

Commit c3b8d2e

Browse files
adriangbclaude
andcommitted
Add optimizer-focused benchmarks for logical plan optimization
Add 17 new benchmarks to sql_planner.rs that stress the logical optimizer across different dimensions: deep join chains (4/8/16 tables), wide expressions (50-200 filter predicates, 100 aggregates, 50 CASE exprs), mixed deep+wide plans, correlated subqueries, unions, distinct, and nested CTEs. Also uncomment TPC-H and TPC-DS full-suite benchmarks. These benchmarks help identify optimizer bottlenecks as plan complexity grows, complementing the existing simple-plan benchmarks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 44dfa7b commit c3b8d2e

1 file changed

Lines changed: 302 additions & 0 deletions

File tree

datafusion/core/benches/sql_planner.rs

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,308 @@ fn criterion_benchmark(c: &mut Criterion) {
634634
c.bench_function("with_param_values_many_columns", |b| {
635635
benchmark_with_param_values_many_columns(&ctx, &rt, b);
636636
});
637+
638+
// ==========================================================================
639+
// Optimizer-focused benchmarks
640+
// These benchmarks are designed to stress the logical optimizer with
641+
// varying plan sizes, expression counts, and node type distributions.
642+
// ==========================================================================
643+
644+
// --- Deep join trees (many plan nodes, few expressions) ---
645+
// Tests optimizer traversal cost as plan node count grows.
646+
// Each join adds ~3 nodes (Join, TableScan, CrossJoin/Filter).
647+
648+
// Register additional tables for join benchmarks
649+
for i in 3..=16 {
650+
ctx.register_table(
651+
&format!("j{i}"),
652+
create_table_provider("x", 10),
653+
)
654+
.unwrap();
655+
}
656+
657+
c.bench_function("logical_join_chain_4", |b| {
658+
b.iter(|| {
659+
logical_plan(
660+
&ctx,
661+
&rt,
662+
"SELECT j3.x0 FROM j3 \
663+
JOIN j4 ON j3.x0 = j4.x0 \
664+
JOIN j5 ON j4.x0 = j5.x0 \
665+
JOIN j6 ON j5.x0 = j6.x0",
666+
)
667+
})
668+
});
669+
670+
c.bench_function("logical_join_chain_8", |b| {
671+
b.iter(|| {
672+
logical_plan(
673+
&ctx,
674+
&rt,
675+
"SELECT j3.x0 FROM j3 \
676+
JOIN j4 ON j3.x0 = j4.x0 \
677+
JOIN j5 ON j4.x0 = j5.x0 \
678+
JOIN j6 ON j5.x0 = j6.x0 \
679+
JOIN j7 ON j6.x0 = j7.x0 \
680+
JOIN j8 ON j7.x0 = j8.x0 \
681+
JOIN j9 ON j8.x0 = j9.x0 \
682+
JOIN j10 ON j9.x0 = j10.x0",
683+
)
684+
})
685+
});
686+
687+
c.bench_function("logical_join_chain_16", |b| {
688+
b.iter(|| {
689+
logical_plan(
690+
&ctx,
691+
&rt,
692+
"SELECT j3.x0 FROM j3 \
693+
JOIN j4 ON j3.x0 = j4.x0 \
694+
JOIN j5 ON j4.x0 = j5.x0 \
695+
JOIN j6 ON j5.x0 = j6.x0 \
696+
JOIN j7 ON j6.x0 = j7.x0 \
697+
JOIN j8 ON j7.x0 = j8.x0 \
698+
JOIN j9 ON j8.x0 = j9.x0 \
699+
JOIN j10 ON j9.x0 = j10.x0 \
700+
JOIN j11 ON j10.x0 = j11.x0 \
701+
JOIN j12 ON j11.x0 = j12.x0 \
702+
JOIN j13 ON j12.x0 = j13.x0 \
703+
JOIN j14 ON j13.x0 = j14.x0 \
704+
JOIN j15 ON j14.x0 = j15.x0 \
705+
JOIN j16 ON j15.x0 = j16.x0 \
706+
JOIN j3 AS j3b ON j16.x0 = j3b.x0 \
707+
JOIN j4 AS j4b ON j3b.x0 = j4b.x0",
708+
)
709+
})
710+
});
711+
712+
// --- Wide expressions (few plan nodes, many expressions) ---
713+
// Tests expression processing overhead in optimizer rules like
714+
// SimplifyExpressions, CommonSubexprEliminate, OptimizeProjections.
715+
716+
// Many WHERE clauses (filter expressions)
717+
{
718+
let predicates: Vec<String> = (0..50)
719+
.map(|i| format!("a{i} > 0"))
720+
.collect();
721+
let query = format!(
722+
"SELECT a0 FROM t1 WHERE {}",
723+
predicates.join(" AND ")
724+
);
725+
c.bench_function("logical_wide_filter_50_predicates", |b| {
726+
b.iter(|| logical_plan(&ctx, &rt, &query))
727+
});
728+
}
729+
730+
{
731+
let predicates: Vec<String> = (0..200)
732+
.map(|i| format!("a{i} > 0"))
733+
.collect();
734+
let query = format!(
735+
"SELECT a0 FROM t1 WHERE {}",
736+
predicates.join(" AND ")
737+
);
738+
c.bench_function("logical_wide_filter_200_predicates", |b| {
739+
b.iter(|| logical_plan(&ctx, &rt, &query))
740+
});
741+
}
742+
743+
// Many aggregate expressions
744+
{
745+
let aggs: Vec<String> = (0..50)
746+
.map(|i| format!("SUM(a{i}), AVG(a{i})"))
747+
.collect();
748+
let query = format!("SELECT {} FROM t1", aggs.join(", "));
749+
c.bench_function("logical_wide_aggregate_100_exprs", |b| {
750+
b.iter(|| logical_plan(&ctx, &rt, &query))
751+
});
752+
}
753+
754+
// Many CASE WHEN expressions (complex expressions)
755+
{
756+
let cases: Vec<String> = (0..50)
757+
.map(|i| {
758+
format!(
759+
"CASE WHEN a{i} > 0 THEN a{i} * 2 ELSE a{i} + 1 END AS r{i}"
760+
)
761+
})
762+
.collect();
763+
let query = format!("SELECT {} FROM t1", cases.join(", "));
764+
c.bench_function("logical_wide_case_50_exprs", |b| {
765+
b.iter(|| logical_plan(&ctx, &rt, &query))
766+
});
767+
}
768+
769+
// --- Mixed: deep plan + wide expressions ---
770+
// This is the worst case for optimizer: many nodes AND many expressions.
771+
772+
c.bench_function("logical_join_4_with_agg_and_filter", |b| {
773+
b.iter(|| {
774+
logical_plan(
775+
&ctx,
776+
&rt,
777+
"SELECT j3.x0, SUM(j4.x1), AVG(j5.x2), COUNT(j6.x3), \
778+
MIN(j3.x4), MAX(j4.x5) \
779+
FROM j3 \
780+
JOIN j4 ON j3.x0 = j4.x0 \
781+
JOIN j5 ON j4.x0 = j5.x0 \
782+
JOIN j6 ON j5.x0 = j6.x0 \
783+
WHERE j3.x1 > 0 AND j4.x2 < 100 AND j5.x3 != j6.x4 \
784+
GROUP BY j3.x0 \
785+
HAVING SUM(j4.x1) > 10 \
786+
ORDER BY j3.x0",
787+
)
788+
})
789+
});
790+
791+
c.bench_function("logical_join_8_with_agg_sort_limit", |b| {
792+
b.iter(|| {
793+
logical_plan(
794+
&ctx,
795+
&rt,
796+
"SELECT j3.x0, j4.x1, j5.x2, \
797+
SUM(j6.x3), AVG(j7.x4), COUNT(j8.x5), \
798+
MIN(j9.x6), MAX(j10.x7) \
799+
FROM j3 \
800+
JOIN j4 ON j3.x0 = j4.x0 \
801+
JOIN j5 ON j4.x0 = j5.x0 \
802+
JOIN j6 ON j5.x0 = j6.x0 \
803+
JOIN j7 ON j6.x0 = j7.x0 \
804+
JOIN j8 ON j7.x0 = j8.x0 \
805+
JOIN j9 ON j8.x0 = j9.x0 \
806+
JOIN j10 ON j9.x0 = j10.x0 \
807+
WHERE j3.x1 > 0 AND j5.x2 < 100 \
808+
GROUP BY j3.x0, j4.x1, j5.x2 \
809+
ORDER BY j3.x0 DESC \
810+
LIMIT 100",
811+
)
812+
})
813+
});
814+
815+
// --- Subqueries (trigger decorrelation rules) ---
816+
// Tests rules like DecorrelatePredicateSubquery, ScalarSubqueryToJoin.
817+
818+
c.bench_function("logical_correlated_subquery_exists", |b| {
819+
b.iter(|| {
820+
logical_plan(
821+
&ctx,
822+
&rt,
823+
"SELECT a0, a1 FROM t1 \
824+
WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.b0 = t1.a0)",
825+
)
826+
})
827+
});
828+
829+
c.bench_function("logical_correlated_subquery_in", |b| {
830+
b.iter(|| {
831+
logical_plan(
832+
&ctx,
833+
&rt,
834+
"SELECT a0, a1 FROM t1 \
835+
WHERE a0 IN (SELECT b0 FROM t2 WHERE t2.b1 = t1.a1)",
836+
)
837+
})
838+
});
839+
840+
c.bench_function("logical_scalar_subquery", |b| {
841+
b.iter(|| {
842+
logical_plan(
843+
&ctx,
844+
&rt,
845+
"SELECT a0, (SELECT MAX(b1) FROM t2 WHERE t2.b0 = t1.a0) AS max_b \
846+
FROM t1",
847+
)
848+
})
849+
});
850+
851+
c.bench_function("logical_multiple_subqueries", |b| {
852+
b.iter(|| {
853+
logical_plan(
854+
&ctx,
855+
&rt,
856+
"SELECT a0, a1 FROM t1 \
857+
WHERE a0 IN (SELECT b0 FROM t2 WHERE b1 > 0) \
858+
AND EXISTS (SELECT 1 FROM t2 WHERE t2.b0 = t1.a0 AND t2.b1 < 100) \
859+
AND a1 > (SELECT AVG(b1) FROM t2)",
860+
)
861+
})
862+
});
863+
864+
// --- UNION queries (test OptimizeUnions, PropagateEmptyRelation) ---
865+
866+
c.bench_function("logical_union_4_branches", |b| {
867+
b.iter(|| {
868+
logical_plan(
869+
&ctx,
870+
&rt,
871+
"SELECT a0, a1 FROM t1 WHERE a0 > 0 \
872+
UNION ALL SELECT a0, a1 FROM t1 WHERE a0 > 10 \
873+
UNION ALL SELECT a0, a1 FROM t1 WHERE a0 > 20 \
874+
UNION ALL SELECT a0, a1 FROM t1 WHERE a0 > 30",
875+
)
876+
})
877+
});
878+
879+
c.bench_function("logical_union_8_branches", |b| {
880+
b.iter(|| {
881+
logical_plan(
882+
&ctx,
883+
&rt,
884+
"SELECT a0, a1 FROM t1 WHERE a0 > 0 \
885+
UNION ALL SELECT a0, a1 FROM t1 WHERE a0 > 10 \
886+
UNION ALL SELECT a0, a1 FROM t1 WHERE a0 > 20 \
887+
UNION ALL SELECT a0, a1 FROM t1 WHERE a0 > 30 \
888+
UNION ALL SELECT a0, a1 FROM t1 WHERE a0 > 40 \
889+
UNION ALL SELECT a0, a1 FROM t1 WHERE a0 > 50 \
890+
UNION ALL SELECT a0, a1 FROM t1 WHERE a0 > 60 \
891+
UNION ALL SELECT a0, a1 FROM t1 WHERE a0 > 70",
892+
)
893+
})
894+
});
895+
896+
// --- DISTINCT (test ReplaceDistinctWithAggregate) ---
897+
898+
c.bench_function("logical_distinct_many_columns", |b| {
899+
let cols: Vec<String> = (0..50).map(|i| format!("a{i}")).collect();
900+
let query = format!("SELECT DISTINCT {} FROM t1", cols.join(", "));
901+
b.iter(|| logical_plan(&ctx, &rt, &query))
902+
});
903+
904+
// --- Nested views / CTEs (deeper plan trees) ---
905+
906+
c.bench_function("logical_nested_cte_4_levels", |b| {
907+
b.iter(|| {
908+
logical_plan(
909+
&ctx,
910+
&rt,
911+
"WITH \
912+
cte1 AS (SELECT a0, a1, a2 FROM t1 WHERE a0 > 0), \
913+
cte2 AS (SELECT a0, a1 FROM cte1 WHERE a1 > 0), \
914+
cte3 AS (SELECT a0 FROM cte2 WHERE a0 < 100), \
915+
cte4 AS (SELECT a0, COUNT(*) AS cnt FROM cte3 GROUP BY a0) \
916+
SELECT * FROM cte4 ORDER BY a0 LIMIT 10",
917+
)
918+
})
919+
});
920+
921+
// --- TPC-H logical plans (uncommented from existing code) ---
922+
// These test real-world query patterns with moderate plan complexity.
923+
924+
c.bench_function("logical_plan_tpch_all", |b| {
925+
b.iter(|| {
926+
for sql in &all_tpch_sql_queries {
927+
logical_plan(&tpch_ctx, &rt, sql)
928+
}
929+
})
930+
});
931+
932+
c.bench_function("logical_plan_tpcds_all", |b| {
933+
b.iter(|| {
934+
for sql in &all_tpcds_sql_queries {
935+
logical_plan(&tpcds_ctx, &rt, sql)
936+
}
937+
})
938+
});
637939
}
638940

639941
criterion_group!(benches, criterion_benchmark);

0 commit comments

Comments
 (0)