[NFC] Add a reverse-postorder queue utility and use it in ConstraintAnalysis flow - #9149
Conversation
tlively
left a comment
There was a problem hiding this comment.
Sounds like you might be able to make this even faster by using Tarjan's algorithm to get the topologically sorted strongly connected components of the CFG. This is O(n) to get the sorted SCCs, then you can simply loop over the SCCs and drive each one to a fixed point without ever revisiting a previous SCC. See src/support/strongly_connected_components.h.
|
Interesting, but let's leave that as a possible followup? |
|
It would be nice to avoid adding this new utility just to rip it out again, but I can review this first if it would significantly slow you down otherwise. |
|
Ok, fair enough, I thought about this over lunch. I don't think SCCs help us here: while (1) {
if (x) {
a();
} else {
b();
}
.. tons of code ..
}All this is one big SCC, but the order inside it matters a lot: we want to process We could perhaps adapt SCC to this, but we'd need to recursively analyze inside SCCs - in this loop example, we'd need to differentiate the If from the stuff after it etc. That will already not be linear. (But even if we did this recursion, we still need RPO order to get the If to happen first - again, it is not an SCC even modulo the loop.) |
|
Hmm, good point. But if there are multiple backedges to the beginning of the loop (e.g. multiple The best of both worlds would be to recursively find SCCs (i.e. loops) and recursively do a reverse post-order traversal of the blocks and nested loops within each loop. The standard terms for this seem to be "Loop Nesting Forest" and "Hierarchical Topological Sorting." The state of the art seems to be described here: https://maskray.me/blog/irreducible-loops. However, we can assume that our CFG is reducible, so we could more simply use a dominator tree to identify all the loop headers and find the nested loops from there, or we could use Bourndoncle's Algorithm to compute the weak topological order (WTO), i.e. the topological order, ignoring backedges, with bracketing around the nested loops, in near-linear time. ... but for now I'll go ahead and review this PR. |
|
Exactly, some kind of nested loop analysis seems right. Could be worth looking at. Actually the relooper does the same thing, I realize (though not sure if we can share the code). Anyhow, this PR is very short and brings a huge win for now! 😄 |
| #ifndef rpo_h | ||
| #define rpo_h |
There was a problem hiding this comment.
| #ifndef rpo_h | |
| #define rpo_h | |
| #ifndef cfg_rpo_h | |
| #define cfg_rpo_h |
This makes CoalesceLocals 15% faster, for similar reasons as #9149.
This makes the pass 25% faster, for similar reasons as #9149.
This is a lot more efficient in the case of large functions: processing in
reverse postorder means we finish up an if/loop before we look onwards.
The
UniqueDeferredQueuetries to do this automatically in a cheapway: when we see a thing twice, we defer it, so it is processed after the
children - but there is no guarantee it works out, depending on how we
traverse the children. The new utility does this deterministically.
This makes ConstraintAnalysis 2-3x faster on a few large testcases I
am looking at. I see smaller wins elsewhere, and I have not found a
single case where this is slower. Even though we now use a priority
queue (logN push/pop), this approach stores the block index and whether
it is in the queue already in the block itself, which is more efficient than
before.
Helps #4165