Skip to content
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

Use a double linked list for free nodes in stable_graph #415

Merged
merged 1 commit into from
Apr 24, 2021

Conversation

ABorgna
Copy link
Member

@ABorgna ABorgna commented Apr 19, 2021

Fixes #412. The previous implementation of extend_with_edges was copied directly from graph and misbehaved.

In a stable graph the function may have to create nodes corresponding to previously-deleted indices.
To do that it has to remove the node from the free_node list, and doing that would have required traversing the list to find the previous node in the chain and modify the pointers.

Instead, I upgraded the free_node list to a doubly linked list using the unused "input edge" field in node.next[1] for the backwards pointer, in addition to the existing forward pointer in node.next[0].
This allows for removing nodes from the free_node list in constant time.

The drawback of this are the additional node indexing operation required each time we have to update the backwards pointer,
but I compared the benchmarks and saw no significant difference.

Benchmark comparison
name                                       master ns/iter  fix ns/iter  diff ns/iter  diff %  speedup
add_100_edges_to_self                      406             400                    -6  -1.48%   x 1.01
add_100_nodes                              18,629          19,368                739   3.97%   x 0.96
add_5_edges_for_each_of_100_nodes          1,003           995                    -8  -0.80%   x 1.01
add_adjacent_edges                         26,117          26,150                 33   0.13%   x 1.00
add_edges_from_root                        26,115          26,305                190   0.73%   x 0.99
bench_add_edge                             381             381                     0   0.00%   x 1.00
bench_inser                                4               4                       0   0.00%   x 1.00
bench_remove                               167             167                     0   0.00%   x 1.00
bigger_edges_in                            34              34                      0   0.00%   x 1.00
bigger_edges_out                           37              37                      0   0.00%   x 1.00
bigger_neighbors_in                        34              34                      0   0.00%   x 1.00
bigger_neighbors_out                       31              31                      0   0.00%   x 1.00
bigger_sccs                                4,888           4,923                  35   0.72%   x 0.99
connected_components_full_dir_bench        545             545                     0   0.00%   x 1.00
connected_components_full_undir_bench      353             352                    -1  -0.28%   x 1.00
connected_components_petersen_dir_bench    256             256                     0   0.00%   x 1.00
connected_components_petersen_undir_bench  202             189                   -13  -6.44%   x 1.07
connected_components_praust_dir_bench      766             736                   -30  -3.92%   x 1.04
connected_components_praust_undir_bench    426             455                    29   6.81%   x 0.94
dijkstra_bench                             1,668,548       1,662,203          -6,345  -0.38%   x 1.00
full_edges_default                         7               7                       0   0.00%   x 1.00
full_edges_in                              13              13                      0   0.00%   x 1.00
full_edges_in                              7               7                       0   0.00%   x 1.00
full_edges_out                             12              12                      0   0.00%   x 1.00
full_edges_out                             20              20                      0   0.00%   x 1.00
full_iso_bench                             1,210           1,209                  -1  -0.08%   x 1.00
full_neighbors_in                          13              13                      0   0.00%   x 1.00
full_neighbors_out                         12              12                      0   0.00%   x 1.00
full_sccs                                  935             935                     0   0.00%   x 1.00
graph_map                                  192             194                     2   1.04%   x 0.99
is_cyclic_undirected_full_dir_bench        63              65                      2   3.17%   x 0.97
is_cyclic_undirected_full_undir_bench      63              65                      2   3.17%   x 0.97
is_cyclic_undirected_petersen_dir_bench    103             105                     2   1.94%   x 0.98
is_cyclic_undirected_petersen_undir_bench  116             117                     1   0.86%   x 0.99
is_cyclic_undirected_praust_dir_bench      99              101                     2   2.02%   x 0.98
is_cyclic_undirected_praust_undir_bench    98              99                      1   1.02%   x 0.99
k_shortest_path_bench                      6,097,948       6,110,103          12,155   0.20%   x 1.00
min_spanning_tree_full_dir_bench           317             328                    11   3.47%   x 0.97
min_spanning_tree_full_undir_bench         213             225                    12   5.63%   x 0.95
min_spanning_tree_petersen_dir_bench       164             173                     9   5.49%   x 0.95
min_spanning_tree_petersen_undir_bench     126             141                    15  11.90%   x 0.89
min_spanning_tree_praust_dir_bench         327             331                     4   1.22%   x 0.99
min_spanning_tree_praust_undir_bench       209             215                     6   2.87%   x 0.97
neighbors_default                          7               7                       0   0.00%   x 1.00
neighbors_in                               8               8                       0   0.00%   x 1.00
neighbors_out                              5               5                       0   0.00%   x 1.00
petersen_iso_bench                         937             942                     5   0.53%   x 0.99
petersen_undir_iso_bench                   709             698                   -11  -1.55%   x 1.02
praust_dir_no_iso_bench                    728,082         721,312            -6,770  -0.93%   x 1.01
praust_undir_no_iso_bench                  746,227         747,843             1,616   0.22%   x 1.00
sccs_graph                                 2,211           2,288                  77   3.48%   x 0.97
sccs_stable_graph                          2,119           2,113                  -6  -0.28%   x 1.00
stable_graph_map                           242             240                    -2  -0.83%   x 1.01
stable_graph_retain_edges                  245             245                     0   0.00%   x 1.00
stable_graph_retain_nodes                  41              41                      0   0.00%   x 1.00

@ABorgna ABorgna force-pushed the fix-extend_with_edges branch 2 times, most recently from 7a1054f to 8fd81ba Compare April 19, 2021 23:46
Fixes a crash ocurring in stable_graph::extend_with_edges.
The double linked list lets us fill any vacant node directly without
having to add the previous free nodes in the list.
@programmerjake
Copy link

hmm, if you don't mind the order in which nodes are reused possibly changing, you can use a singly-linked free list with constant-time insertion and removal:

// using pointers instead of node indexes for easier writing
struct Node {
    next: Option<Box<Node>>,
}

struct FreeList {
    head: Option<Box<Node>>,
}

impl FreeList {
    fn add_free_node(&mut self, node: Box<Node>) {
        node.next = self.head.take();
        self.head = Some(node);
    }
    fn remove_free_node(&mut self) -> Option<Box<Node>> {
        let mut node = self.head.take()?;
        self.head = node.next.take();
        Some(node)
    }
}

@ABorgna
Copy link
Member Author

ABorgna commented Apr 20, 2021

Yes, but the problem is that extend_with_edges requires non-head removals.

Consider the test example:

let mut gr = StableGraph::<_, _>::default();
let a = gr.add_node("a");
let b = gr.add_node("b");
let c = gr.add_node("c");
let _d = gr.add_node("d");
gr.remove_node(a);
gr.remove_node(b);
gr.remove_node(c);

gr.extend_with_edges(vec![(0, 3, ())]);

Before calling the last method, the graph has a single node with index 3 and a free_node list 2 → 1 → 0.
We need to create a vertex with index 0 to add the edge, even though it is not the head of the list.

That is where you have to do the O(n) singly linked list node removal, or use a doubly linked list.

@programmerjake
Copy link

Before calling the last method, the graph has a single node with index 3 and a free_node list 2 → 1 → 0.
We need to create a vertex with index 0 to add the edge, even though it is not the head of the list.

That is where you have to do the O(n) singly linked list node removal, or use a doubly linked list.

Ah, so you need the doubly-linked list since you're removing arbitrary nodes from the middle of the list. makes sense!

@bluss
Copy link
Member

bluss commented Apr 20, 2021

Hi everyone @ABorgna @programmerjake @saolof @jkeljo @mtreinish @pnevyk @sebk @XVilka @jrraymond @jmcomets @waywardmonkeys @TheZalli

Petgraph has seen an increase in activity and that's really nice. Long ago I created it. I'm sorry that I haven't been able to keep up with maintaining or managing releases. We've had maintainers step up and that has been great, and I think we need to talk about that again. We already have an organization but the designated maintainers (including me) don't seem to have so much time, which is a shame.

This PR looks really good and clicking merge would be easy, but petgraph needs a little bit more: maintainers and making releases (there is no point in merging things if they don't eventually end up in a nice, working release). Petgraph needs ownership, not just someone that wants to help 🙂, that's what I see at least. I wouldn't mind signing up new maintainers, if @XVilka agrees. Thanks for reading.

@ABorgna
Copy link
Member Author

ABorgna commented Apr 20, 2021

I'd be happy to sign up as a maintainer.
I see there is some PR backlog and a 0.5.2 milestone with some changes that haven't been published. Did you have some plans for the next release ?

@saolof
Copy link
Contributor

saolof commented Apr 20, 2021

I'm currently busy finishing my phd thesis so I can graduate, but I'd be happy to sign up as a maintainer as soon as that's done (i.e. I should have loads of free time in June).

@TheZalli
Copy link
Collaborator

I really like this library and use it in my projects often, so I'd happily accept to become a maintainer. I might not always be regularly consistent with the time I spent on programming work, but I think I could do work and design for this library at times, even on bigger things.

I don't have any organization experience about managing libraries like these, but if someone made a list of work I could work on that.

@bluss
Copy link
Member

bluss commented Apr 20, 2021

That's great. We need to hear from @XVilka before continuing. I'll stress that I want this library to grow past me, and I don't want to be a speedbump for it (I'm a grumpy maintainer 🙂). If we can we should plan out if the next release is breaking or non-breaking, get unreleased changes released and then integrate new changes and get those released too.

@XVilka
Copy link
Member

XVilka commented Apr 21, 2021 via email

@XVilka
Copy link
Member

XVilka commented Apr 21, 2021

This is what I had in mind for the next milestone: https://github.com/petgraph/petgraph/milestone/4

@bluss
Copy link
Member

bluss commented Apr 23, 2021

Great! I've invited @ABorgna and @TheZalli.

I'm around but I don't intend to "assert ownership" for lack of a better word. I can't really plan and execute releases for petgraph right now -- so it should be open for maintainers that want to, to do that. I won't click merge here -- I encourage maintainers to work together and also feel personally empowered. Let me know who needs release (crates.io) powers or other upgraded powers. We can keep in mind that there is a petgraph organization and a petgraph repo - where you choose to spend time or care about is of course voluntary, it's just another level of managing access and I'm not really that good at it.

If you need to contact me there is contact info in my profile (scarce) and I'm on #rust-sci:matrix.org and sometimes in the rust discords.

@ABorgna
Copy link
Member Author

ABorgna commented Apr 23, 2021

Thank you! I'll start tackling the issue queue, and maybe later we can discuss what should go into the next release.

@TheZalli do you want to check and merge my PR ?

@TheZalli
Copy link
Collaborator

Thank you! I'll start tackling the issue queue, and maybe later we can discuss what should go into the next release.

@TheZalli do you want to check and merge my PR ?

This is the first time I'm checking a PR, but looking over the commit I didn't see anything wrong. It's passed all checks and even the benchmark showed no big slowdown so I'll merge it.

@TheZalli TheZalli merged commit e6cd3f2 into petgraph:master Apr 24, 2021
@bluss
Copy link
Member

bluss commented Apr 25, 2021

Thanks! You can of course ask me, if you want to have input on something 🙂

@ABorgna ABorgna deleted the fix-extend_with_edges branch April 25, 2021 13:42
teuron pushed a commit to teuron/petgraph that referenced this pull request Oct 9, 2022
Fixes a crash occurring in `stable_graph::extend_with_edges`.
The double linked list lets us fill any vacant node directly without
having to add the previous free nodes in the list.
teuron pushed a commit to teuron/petgraph that referenced this pull request Oct 9, 2022
Fixes a crash occurring in `stable_graph::extend_with_edges`.
The double linked list lets us fill any vacant node directly without
having to add the previous free nodes in the list.
@ABorgna ABorgna mentioned this pull request Jan 7, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Panic in StableGraph::extend_with_edges
6 participants