Performance (3/5): skip the checkPrefix prefix-walk when no transformer is present#3836
Draft
unp1 wants to merge 1 commit into
Draft
Performance (3/5): skip the checkPrefix prefix-walk when no transformer is present#3836unp1 wants to merge 1 commit into
unp1 wants to merge 1 commit into
Conversation
…former RewriteTaclet.checkPrefix walks the whole root-to-position prefix (PIOPathIterator) at every position it is asked about. During taclet-index construction / one-step simplification of a deep term that is O(depth) per position over ~d positions, i.e. O(d^2) -- the dominant cost on deeply nested terms such as chained if-then-else (a JFR profile showed 54% self-time in checkPrefix on a trivial 3-node proof). For an unrestricted (NONE) rewrite taclet -- the common case -- the only prefix-dependent outcome of that walk is a veto when a Transformer occurs on the path; the update/polarity/modality handling is guarded by a non-NONE restriction and the polarity is discarded. So if the formula provably contains no Transformer anywhere, no prefix can, and the walk can be skipped. "Formula contains a Transformer" is computed once and cached per term (JTerm.containsTransformerRecursive, mirroring containsJavaBlockRecursive), giving O(1) amortized and dropping the per-position prefix cost from O(depth) to O(1) in the transformer-free case; the O(d^2) on deep terms becomes O(d). Behaviour-preserving: it only short-circuits a provably-equivalent case; restricted taclets and transformer-bearing formulas still take the full walk.
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.
This PR is 3/5 of a performance series that prepares a clearer match → evaluate → apply pipeline before larger optimisations; it builds on the compiled matcher (1/5, #3831).
📖 Developer docs: Performance Optimizations (3.1)
Intended Change
RewriteTaclet.checkPrefixwalks the entire root-to-position prefix (PIOPathIterator) at every position it is asked about. During taclet-index construction / one-step simplification of a deep term that is O(depth) per position over ~d positions — i.e. O(d²), the dominant cost on deeply nested terms such as chained if-then-else. A JFR profile on a trivial 3-node proof showed 54% self-time incheckPrefix.Key observation: for an unrestricted (
NONE) rewrite taclet — the common case — the only prefix-dependent outcome of that walk is a veto when aTransformeroccurs on the path. The update-context / polarity / modality handling is all guarded by a non-NONErestriction, and the computed polarity is discarded. So if the formula provably contains noTransformeranywhere, no prefix can either, and the walk can be skipped entirely."Formula contains a
Transformer" is computed once and cached per term (JTerm.containsTransformerRecursive, mirroring the existingcontainsJavaBlockRecursive). That makes the check O(1) amortized and drops the per-position prefix cost from O(depth) to O(1) in the transformer-free case; the O(d²) on deep terms becomes O(d).Behaviour-preserving: it only short-circuits a provably-equivalent case. Restricted taclets and transformer-bearing formulas still take the full walk.
flowchart LR A["checkPrefix"] --> B{"no Transformer?"} B -- yes --> C["skip walk (O of 1)"] B -- no --> D["full walk (O of depth)"]Type of pull request
Performance
Measured on the 6-problem
perfTestset (median of 3 runs; byte-identical proofs, node counts unchanged). Plus a synthetic deep nested-if-then-else microbenchmark that isolates the O(d²)→O(d) effect.Node counts identical to
main(proofs unchanged). The effect on these six real-world problems is marginal (~1.01×) because their terms are shallow —checkPrefixis not their bottleneck. The change targets the O(d²) blow-up on deep terms: on a synthetic depth-400 nested if-then-else it is ~1407 ms → ~526 ms (≈2.7×). It is included because (a) it is essentially free and behaviour-preserving, and (b) deep-term proofs do occur in practice.Ensuring quality
-Dkey.incrementalPrefixgate is removed; fast path is unconditional).📖 Conceptual overview in the developer docs: Performance Optimizations (3.1)
Additional information and contact(s)
This PR has been done with AI tooling support.
The contributions within this pull request are licensed under GPLv2 (only) for inclusion in KeY.