perf(topological_sort): traverse iteratively and drop the redundant sets - #800
Merged
samueltardieu merged 1 commit intoSep 9, 2026
Merged
Conversation
`visit` recurses once per node, so a deep graph overflows the stack: a chain of 200 000 nodes is enough, and so is a random DAG of 60 000. Walk an explicit stack instead. The bookkeeping around it was doing the same work several times over: - `marked` and `temp` are nested: a node is in `temp` from the moment it is reached and in `marked` once it is finished, so `marked` is a subset of `temp` and every node was hashed into both. One map from node to "finished?" answers both questions in a single lookup. - The outer loop repeatedly took an arbitrary key out of a `HashSet` of roots that `visit` was emptying as it went, so each call rescanned the buckets left behind by the ones before it. Walking the caller's slice needs no set at all, and with it goes a clone of every root. - Both sets used the default hasher, unlike the rest of the crate. `topological_sort_into_groups` keeps its structure and just moves to the same hasher. `successors` is still called exactly once per node, which the existing `complexity` test checks. Orders are checked for validity against 200 random DAGs. On 60 000 nodes: about 76% off `topological_sort`, and about 33% off `topological_sort_into_groups`.
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.
Part of #795, which covers the same defect in
strongly_connected_componentsas well; that half is #799, and the issue can be closed once both have landed.visitrecursed once per node, so a deep graph overflowed the stack: a chain of 200 000 nodes is enough, and so is a random DAG of 60 000. This replaces the recursion with an explicit stack.The bookkeeping around it was doing the same work more than once:
markedandtempare nested — a node is intempfrom the moment it is reached and inmarkedonce it is finished, somarkedis a subset oftempand every node was hashed into both. One map from node to "finished?" answers both questions in a single lookup.HashSetof roots thatvisitwas emptying, so each call rescanned the buckets left behind by the ones before it. Walking the caller's slice needs no set at all, and with it goes a clone of every root.topological_sort_into_groupskeeps its structure and only changes hasher.Checking
successorsis still called exactly once per node, which the existingcomplexitytest covers. Orders are checked edge by edge against 200 random DAGs, and a regression test runs a 200 000 node chain on a deliberately small 1 MiB stack.On 60 000 nodes: about 76% off
topological_sort, about 33% offtopological_sort_into_groups.All timings measured on Windows 11, Intel Core Ultra 7 265K, 64 GB RAM.