zhuqi-lucas opened a new pull request, #23532:
URL: https://github.com/apache/datafusion/pull/23532

   ## Which issue does this PR close?
   
   - Closes #.
   
   ## Rationale for this change
   
   `DynamicFilterPhysicalExpr::current()` is invoked per batch on the RowFilter 
path (via `PhysicalExpr::evaluate`). It calls `remap_children`, which does a 
`transform_up` tree walk. For dynamic filters that carry a large `InListExpr` — 
e.g. `HashJoinExec` pushing down a `col IN build_keys` list of ~150+ values — 
that walk is dominated by `InListExpr::with_new_children` cloning the whole 
list on every call.
   
   The `inner.generation` only changes when the filter is `update`d — once per 
HashJoin build, or once per TopK threshold refresh. Between updates every 
`current()` call recomputes an identical remapped tree. Caching per generation 
lets per-batch calls short-circuit the walk.
   
   ### Measured impact (TPCH SF1, 10 iterations)
   
   | | HEAD `pushdown=true` | This PR `pushdown=true` | Baseline 
`pushdown=false` |
   |---|---|---|---|
   | **Q17** | 128 ms | **53 ms** (**2.4× faster**) | 55 ms |
   | Q18 | 69 ms | 56 ms | 49 ms |
   | Total (22 queries) | 743 ms | 644 ms (**-13%**) | 531 ms |
   
   Q17 (Nation-in-orders subquery) matches the `pushdown=false` baseline after 
the fix — the DynamicFilter tax is fully eliminated on that shape. Row counts 
identical across all 22 queries pre/post fix.
   
   Profile before the fix: `InListExpr::with_new_children` and its `drop_glue` 
sat at the top of Q17's samply flamegraph.
   
   ## What changes are included in this PR?
   
   - Add `current_cache: Arc<RwLock<Option<(u64, Arc<dyn PhysicalExpr>)>>>` on 
`DynamicFilterPhysicalExpr` (aliased as `CurrentExprCache` to keep the type 
readable).
   - `current()`: read the current `(expr, generation)` from `inner`, return 
the cached `Arc` if the cached generation matches; otherwise recompute via 
`remap_children` and store the new pair under a write lock.
   - Reset semantics:
     - `update()` bumps `inner.generation`, so the next `current()` misses the 
cache and recomputes.
     - `with_new_children` clones the outer struct with new 
`remapped_children`; the derived filter gets its own fresh cache slot.
     - `from_parts` (proto deserialization) starts with an empty cache.
   - No public API changes.
   
   ## Are these changes tested?
   
   Yes — three new regression tests in `expressions::dynamic_filters::test`:
   
   - `test_current_cache_hits_within_generation` — three consecutive 
`current()` calls return the same `Arc` (pointer-equal) at the same generation, 
proving the remap walk did not re-run.
   - `test_current_cache_invalidates_on_update` — after `update()` bumps the 
generation, `current()` returns a fresh `Arc` (not pointer-equal, and stringly 
distinct).
   - `test_current_cache_is_per_derived_filter` — two filters derived via 
`with_new_children` with different `remapped_children` produce distinct cached 
`Arc`s and each hits its own cache on subsequent calls.
   
   All 21 tests in `expressions::dynamic_filters` (18 existing + 3 new) pass.
   
   ## Are there any user-facing changes?
   
   No. Internal caching only. No public API changes; behavior is identical 
modulo the generation-tied fast path.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to