-
Notifications
You must be signed in to change notification settings - Fork 3.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Arith][Refactor] Extract And/Or/Not handling from RewriteSimplifier #12942
Conversation
Previously, rewrites of boolean expressions were handled directly within `RewriteSimplifier` visitors. This commit extracts the handling of `And`, `Or`, and `Not` nodes into an independent internal utility. The `RewriteBooleanOperators` utility can simplifies these nodes, assuming that the arguments to these expressions are already simplified. (e.g. Finding the negation of `(a < b)` without needing to recurse into `a` or `b`.) This will be used as part of additional simplifications required by [buffer layout padding](apache#12261). Because no external functionality is added or changed by this refactor, this commit does not introduce any additional unit tests.
Thanks @Lunderberg . RewriteSimplifier is in the heart of a lot of things, so changes to this module should be done with extra amount of care. For example, in some of our cases we have recursive calls into simplify that further triggers other rewrite simplifications of the expressions within the boolean expr ( We can see the values of introducing boolean simplification as being described. For extra caution, it might be helpful to simply introduce a bit of duplication of rules and have an independent implementation here. Since SimplifyBooleanOps are used in more limited cases. Additionally, is it OK to simply invoke the full simplification instead? It might be useful to discuss the usecase scenarios to see if we can also retrofit to some of existing simplifiers |
@tqchen Thank you, and I definitely agree that any changes to RewriteSimplifier should be done with care, because of how foundational it is, but also any improvements to it may yield improvements across the codebase. I didn't do so for this PR, because it isn't intended to alter any functionality, but any that do are going to be behind feature flags to avoid incidental breakage or unnecessary CPU usage.
Regarding the This should also improve the performance. On the main branch, the expression
1. Visit `!(a && (b || c))`
1. Visit `a && (b || c)`
1. Visit `a`
2. Visit `b || c`
1. Visit `b`
2. Visit `c`
3. Return `b || c`
3. Return `a && (b || c)`
2. Recursive rewrite, `!(a && (b || c))` becomes `(!a) || (!(b||c))`
3. Visit `(!a) || (!(b||c))`
1. Visit of `!a`
1. Visit `a`
2. Return `!a`
2. Visit `(!(b||c))`
1. Visit `b||c`
1. Visit `b`
2. Visit `c`
3. Return `b||c`
2. Recursive rewrite, `!(b||c)` becomes `(!b) && (!c)`
3. Visit `(!b) && (!c)`
1. Visit `!b`
1. Visit `b`
2. Return `!b`
2. Visit `!c`
1. Visit `c`
2. Return `!c`
3. Return `(!b) && (!c)`
4. Return `(!b) && (!c)`
3. Return `(!a) || ((!b) && (!c))`
4. Return `(!a) || ((!b) && (!c))`
1. RW, Visit `!(a && (b || c))`
1. RW, Visit `a && (b || c)`
1. RW, Visit `a`
2. RW, Visit `b || c`
1. RW, Visit `b`
2. RW, Visit `c`
3. Return `b || c`
3. Return `a && (b || c)`
2. Delegate to BoolRW
3. Recursive rewrite, `!(a && (b || c))` becomes `(!a) || (!(b||c))`
4. BoolRW, Visit `(!a) || (!(b||c))`
1. BoolRW, Visit `!a`
2. BoolRW, Visit `(!(b||c))`
1. BoolRW, Visit `b||c`
2. Recursive rewrite, `!(b||c)` becomes `(!b) && (!c)`
3. BoolRW, Visit `(!b) && (!c)`
1. Visit `!b`
2. Visit `!c`
3. Return `(!b) && (!c)`
4. Return `(!b) && (!c)`
3. Return `(!a) || ((!b) && (!c))`
5. Return `(!a) || ((!b) && (!c))`
In the short-term, I agree that a copy/paste of the relevant portions would be the safest, and would minimize the scope of any changes. In the long-term, I'd worry about having multiple duplicate implementations, whether improvements/bugfixes in one get propagated over to the other, or the impact of requiring a developer to know which of multiple related implementations should be called.
For the test cases I've run, it is okay to use the full simplification rather than limiting it to boolean expressions. That said, the test cases were designed to test the functionality being added, and so I'm not sure the final cost of the additional visits. |
Thanks @Lunderberg for detailed analysis. Get it. Given full simplification is also applicable. It also depends on how many times simplify is being called in the related places. If they are constant, perhaps we could use full simplification instead for now, then circle back if we have places where we clearly see there is a need. |
That makes sense, and agreed as a path forward. Closing this PR until and unless it becomes necessary or the performance difference becomes much larger. |
Thanks @Lunderberg for constructive discussions |
Absolutely, and thank you for the same, @tqchen! |
Previously, rewrites of boolean expressions were handled directly within
RewriteSimplifier
visitors. This commit extracts the handling ofAnd
,Or
, andNot
nodes into an independent internal utility. TheRewriteBooleanOperators
utility can simplifies these nodes, assuming that the arguments to these expressions are already simplified. (e.g. Finding the negation of(a < b)
without needing to recurse intoa
orb
.) This will be used as part of additional simplifications required by buffer layoutpadding.
Because no external functionality should be added or changed by this refactor, this commit does not introduce any additional unit tests.