已开启
udf-chain-physical-fusion #52
udf-chain-physical-fusion #52
已开启
3stone创建于 7月24日
5 个文件变更+105-3
@@ -529,10 +529,16 @@ impl LogicalPlan {
529 Self::UDFProject(UDFProject {529 Self::UDFProject(UDFProject {
530 expr,530 expr,
531 passthrough_columns,531 passthrough_columns,
532+ udf_properties,
532 ..533 ..
533 }) => Self::UDFProject(534 }) => Self::UDFProject(
534- UDFProject::try_new(input.clone(), expr.clone(), passthrough_columns.clone())535+ UDFProject::try_new_fused(
535- .unwrap(),536+ input.clone(),
537+ expr.clone(),
538+ passthrough_columns.clone(),
539+ udf_properties.clone(),
540+ )
541+ .unwrap(),
536 ),542 ),
537 Self::Filter(Filter { predicate, .. }) => {543 Self::Filter(Filter { predicate, .. }) => {
538 Self::Filter(Filter::try_new(input.clone(), predicate.clone()).unwrap())544 Self::Filter(Filter::try_new(input.clone(), predicate.clone()).unwrap())
@@ -63,6 +63,34 @@ impl UDFProject {
63 })63 })
64 }64 }
65 65 
66+ /// Construct a UDF project whose expression contains a compatible chain
67+ /// of Python UDFs. The physical-fusion optimizer has already checked that
68+ /// all UDFs share the supplied scheduling and error-handling properties.
69+ pub(crate) fn try_new_fused(
70+ input: Arc<LogicalPlan>,
71+ expr: ExprRef,
72+ passthrough_columns: Vec<ExprRef>,
73+ udf_properties: UDFProperties,
74+ ) -> Result<Self> {
75+ let output_field = expr.to_field(&input.schema())?;
76+ let fields = passthrough_columns
77+ .iter()
78+ .map(|e| e.to_field(&input.schema()))
79+ .chain(std::iter::once(Ok(output_field)))
80+ .collect::<DaftResult<Vec<_>>>()?;
81+ 
82+ Ok(Self {
83+ plan_id: None,
84+ node_id: None,
85+ input,
86+ expr,
87+ udf_properties,
88+ passthrough_columns,
89+ projected_schema: Arc::new(Schema::new(fields)),
90+ stats_state: StatsState::NotMaterialized,
91+ })
92+ }
93+ 
66 pub fn with_plan_id(mut self, plan_id: usize) -> Self {94 pub fn with_plan_id(mut self, plan_id: usize) -> Self {
67 self.plan_id = Some(plan_id);95 self.plan_id = Some(plan_id);
68 self96 self
@@ -9,7 +9,7 @@ use super::{
9 rules::{9 rules::{
10 DetectMonotonicId, DropIntoBatches, DropRepartition, EliminateCrossJoin, EliminateOffsets,10 DetectMonotonicId, DropIntoBatches, DropRepartition, EliminateCrossJoin, EliminateOffsets,
11 EliminateSubqueryAliasRule, EnrichWithStats, ExtractWindowFunction, FilterNullJoinKey,11 EliminateSubqueryAliasRule, EnrichWithStats, ExtractWindowFunction, FilterNullJoinKey,
12- LiftProjectFromAgg, MaterializeScans, OptimizerRule, PushDownAggregation,12+ FuseUDFProjects, LiftProjectFromAgg, MaterializeScans, OptimizerRule, PushDownAggregation,
13 PushDownAntiSemiJoin, PushDownFilter, PushDownJoinPredicate, PushDownLimit,13 PushDownAntiSemiJoin, PushDownFilter, PushDownJoinPredicate, PushDownLimit,
14 PushDownProjection, PushDownShard, ReorderJoins, RewriteCountDistinct, RewriteOffset,14 PushDownProjection, PushDownShard, ReorderJoins, RewriteCountDistinct, RewriteOffset,
15 ShardScans, SimplifyExpressionsRule, SimplifyNullFilteredJoin, SplitExplodeFromProject,15 ShardScans, SimplifyExpressionsRule, SimplifyNullFilteredJoin, SplitExplodeFromProject,
@@ -203,6 +203,12 @@ impl OptimizerBuilder {
203 vec![Box::new(PushDownProjection::new())],203 vec![Box::new(PushDownProjection::new())],
204 RuleExecutionStrategy::FixedPoint(None),204 RuleExecutionStrategy::FixedPoint(None),
205 ),205 ),
206+ // Collapse compatible UDF chains after SplitUDFs has made their
207+ // task boundaries explicit.
208+ RuleBatch::new(
209+ vec![Box::new(FuseUDFProjects::new())],
210+ RuleExecutionStrategy::FixedPoint(None),
211+ ),
206 // --- Push down aggregations ---212 // --- Push down aggregations ---
207 RuleBatch::new(213 RuleBatch::new(
208 vec![Box::new(PushDownAggregation::new(214 vec![Box::new(PushDownAggregation::new(
@@ -0,0 +1,60 @@
1+use std::{collections::HashMap, sync::Arc};
2+ 
3+use common_error::DaftResult;
4+use common_treenode::{Transformed, TreeNode};
5+use daft_dsl::optimization::replace_columns_with_expressions;
6+ 
7+use super::OptimizerRule;
8+use crate::{LogicalPlan, ops::UDFProject};
9+ 
10+/// Fuse adjacent, semantically compatible Python UDF projects into one
11+/// physical project. This removes an actor/task and materialization boundary
12+/// without changing the user-visible expression graph.
13+#[derive(Default, Debug)]
14+pub struct FuseUDFProjects {}
15+ 
16+impl FuseUDFProjects {
17+ pub fn new() -> Self {
18+ Self {}
19+ }
20+ 
21+ fn compatible(parent: &UDFProject, child: &UDFProject) -> bool {
22+ if !parent.passthrough_columns.is_empty() || !child.passthrough_columns.is_empty() {
23+ return false;
24+ }
25+ 
26+ let mut parent_props = parent.udf_properties.clone();
27+ let mut child_props = child.udf_properties.clone();
28+ // Function names are diagnostic metadata, not execution semantics.
29+ parent_props.name.clear();
30+ child_props.name.clear();
31+ parent_props == child_props
32+ }
33+}
34+ 
35+impl OptimizerRule for FuseUDFProjects {
36+ fn try_optimize(&self, plan: Arc<LogicalPlan>) -> DaftResult<Transformed<Arc<LogicalPlan>>> {
37+ plan.transform_up(|node| {
38+ let LogicalPlan::UDFProject(parent) = node.as_ref() else {
39+ return Ok(Transformed::no(node));
40+ };
41+ let LogicalPlan::UDFProject(child) = parent.input.as_ref() else {
42+ return Ok(Transformed::no(node));
43+ };
44+ if !Self::compatible(parent, child) {
45+ return Ok(Transformed::no(node));
46+ }
47+ 
48+ let replacements = HashMap::from([(child.expr.name().to_string(), child.expr.clone())]);
49+ let fused_expr = replace_columns_with_expressions(parent.expr.clone(), &replacements);
50+ let mut fused = UDFProject::try_new_fused(
51+ child.input.clone(),
52+ fused_expr,
53+ vec![],
54+ parent.udf_properties.clone(),
55+ )?;
56+ fused.stats_state = parent.stats_state.clone();
57+ Ok(Transformed::yes(Arc::new(LogicalPlan::UDFProject(fused))))
58+ })
59+ }
60+}
@@ -7,6 +7,7 @@ mod eliminate_subquery_alias;
7mod enrich_with_stats;7mod enrich_with_stats;
8mod extract_window_function;8mod extract_window_function;
9mod filter_null_join_key;9mod filter_null_join_key;
10+mod fuse_udf_projects;
10mod granular_projections;11mod granular_projections;
11mod lift_project_from_agg;12mod lift_project_from_agg;
12mod materialize_scans;13mod materialize_scans;
@@ -37,6 +38,7 @@ pub use eliminate_subquery_alias::EliminateSubqueryAliasRule;
37pub use enrich_with_stats::EnrichWithStats;38pub use enrich_with_stats::EnrichWithStats;
38pub use extract_window_function::ExtractWindowFunction;39pub use extract_window_function::ExtractWindowFunction;
39pub use filter_null_join_key::FilterNullJoinKey;40pub use filter_null_join_key::FilterNullJoinKey;
41+pub use fuse_udf_projects::FuseUDFProjects;
40pub use granular_projections::SplitGranularProjection;42pub use granular_projections::SplitGranularProjection;
41pub use lift_project_from_agg::LiftProjectFromAgg;43pub use lift_project_from_agg::LiftProjectFromAgg;
42pub use materialize_scans::MaterializeScans;44pub use materialize_scans::MaterializeScans;