-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
Perf improvement to subgraph selection #4155
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fantastic. On my machine, using the sample performance project, it reduces the duration of the get_subset_graph
method from 5.5s to 0.5-1s. I'll take a 5-10x speedup any day of the week.
Because the work to add edges happens each time we remove an unselected node from the graph, this approach means that get_subset_graph
is faster when selecting all nodes (~0.5s locally) than when selecting only one node (~1s) in a large project. That makes sense. I'm only bringing it up because we should, when necessary, optimize for speedups when few nodes are selected (development) rather than many (production). In this case, we get to have it faster all across the board :)
I haven't managed to break this, all the tests seem to be passing, it seems like it's doing the right thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great to me
source_nodes = [x for x, _ in new_graph.in_edges(node)] | ||
target_nodes = [x for _, x in new_graph.out_edges(node)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense for these to be sets?
source_nodes = [x for x, _ in new_graph.in_edges(node)] | |
target_nodes = [x for _, x in new_graph.out_edges(node)] | |
source_nodes = {x for x, _ in new_graph.in_edges(node)} | |
target_nodes = {x for _, x in new_graph.out_edges(node)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In theory, lists are a little faster in this scenario (iteration as opposed to set membership)
core/dbt/graph/graph.py
Outdated
new_edges = product(source_nodes, target_nodes) | ||
new_edges = [ | ||
(source, target) for source, target in new_edges if source != target | ||
] # removes cyclic refs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! Love being able to read this instead of looking up what nx.algorithms.transitive_closure
does every time as well.
I'm wondering how this compares to networkx.algorithms.dag.transitive_closure_dag which implements a faster transitive closure alg given an acyclical graph. Using ^^ would have the same performance impact regardless of how many nodes are included. I think it is a tradeoff at the end of the day where you take the performance hit (as @jtcohen6 mentioned). Do you have a sense of how this compares to this PR? |
@kwigley Nice find!! I just tried out on my local machine (using the same sample perf project, 2k models + 6k tests), and it looks like that one-line change ( That seems like an uncontroversial improvement. There's no way it can be slower, right? And it preserves consistent behavior no matter how many nodes are being selected. |
4a5780e
to
c00a13c
Compare
@kwigley and @jtcohen6 I made some benchmarks using the performance project!
Note that this is testing the entire I like the approach they take in * they use a topo sort + graph distance calculation instead of a graph iteration + edge detection. |
Perf improvement to get_subset_graph Co-authored-by: Ian Knox <[email protected]>
* Perf improvement to subgraph selection (#4155) Perf improvement to get_subset_graph Co-authored-by: Ian Knox <[email protected]> * Add extra graph edges for `build` only (#4143) * Resolve extra graph edges for build only * Fix flake8 * Change test to reflect functional change * Rename method + args. Add changelog entry * Any subset, strict or not (#4160) * Fix test backport Co-authored-by: leahwicz <[email protected]>
resolves #4135
Description
This performance improvement changes the way we select our graph subsets. The new algorithm replaces the use of a transitive closure (NP-Hard) and implements a combined transitive reduction + subtraction. I haven't done the math to determine complexity, but it should be at least P (or even lower, my complexity chops are a bit rusty).
In a practical setting it's reduced the time to run for the performance project from ~14.77s to ~11.17 seconds (~ 25% faster) when running on my macbook. I believe the improvement should scale with the size of the projects. The larger the graph, the larger the gain.
Checklist
CHANGELOG.md
and added information about my change