Thread Scope through the optimizer, coercion and partitioning - #9338
Draft
mhk197 wants to merge 1 commit into
Draft
Thread Scope through the optimizer, coercion and partitioning#9338mhk197 wants to merge 1 commit into
Scope through the optimizer, coercion and partitioning#9338mhk197 wants to merge 1 commit into
Conversation
Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Merging this PR will improve performance by 17.52%
Performance Changes
Tip Curious why this is faster? Comment Comparing Footnotes
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #9257.
Why
optimize,coerce_expressionandpartitionall took a&DTypeand typed every node against it. That works for a flat tree but has two consequences:Variableis untypeable, so each pass grew its own lambda bail-outWhat
All three now take
impl Into<Scope>, andExpression::return_dtyperesolvesRootandVariableagainst that scope instead of bailing on the latter.DType: Into<Scope>yields a scope binding no frames, so existing callers pass a root dtype unchanged and still resolve no variables — the fail-closed property is kept, but it is now a consequence of the scope being empty rather than a special case.A caller that does supply a frame can now resolve variables through these passes. That is what struct-expression partitioning needs in order to use real variables instead of encoding its environment as a synthetic struct.
SimplifyCacheis goneIts typing logic reduced to
expr.return_dtype(&scope)oncereturn_dtypebecame scope-aware, leaving only the memo. That memo was keyed onExpression, whoseHashwalks the entire subtree — so each lookup paid the same O(subtree) cost it was meant to save.Measured on
expr_optimize(or-chain of 200, 4 runs each, medians):Removing it is the same or slightly faster. It was also a latent correctness hazard: keyed on the expression alone, it goes unsound the moment the scope varies mid-walk.
Lambdas are still a boundary
Not an oversight. A lambda's parameter dtypes come from whoever applies it, and an unbound tree records no applier, so there is no frame to push. A higher-order function knows those types; at that point each bail-out becomes a
push_frame. Until then, coercion and constant-folding do not run inside a lambda body — conservative, and currently unobservable since nothing constructs an applied lambda.Checks
cargo build --workspacecargo nextest run -p vortex-array— 3283 passedcargo nextest run -p vortex-layout -p vortex-file -p vortex-scan -p vortex-datafusion -p vortex-geo -p vortex-btrblocks— 848 passedcargo test --doc -p vortex-array— 73 passedRUSTDOCFLAGS="-D warnings" cargo doc --no-deps --workspacecargo clippy --all-targets --all-features— cleancargo +nightly fmt --all,git diff --checkcargo bench -p vortex-array --bench expr_optimize— no regressionNew tests cover a variable resolving through a frame in both
optimizeandcoerce, and being rejected without one.