Skip to main content

Module snapshot

Module snapshot 

Source
Expand description

The graph flattened into dense arrays, which is the shape every algorithm wants (11 section 8).

crate::Adjacency is built for a graph that is being changed: a run is found by hashing the node and the label, it is grown and shrunk in place, and a node is whatever u64 the caller decided to call it. That is the right structure for a traversal, which touches a handful of runs and wants each one to be a probe and a sequential read, and it is the wrong structure for PageRank, which touches every run twenty times and wants node zero’s neighbours to be the first thing in the array.

So an algorithm runs over one of these instead. It is the same edges, renumbered from zero, in one flat CSR per direction, taken at a point in time. Nothing in here can change the graph and nothing here notices when the graph changes, which is the trade: a snapshot goes stale, and in exchange every algorithm below it is arrays and index arithmetic with no hashing on any inner loop.

use yo_graph::{Graph, NO_PROPS, Snapshot};

const FOLLOWS: u32 = 1;

let mut g = Graph::new();
g.link(10, 20, FOLLOWS, NO_PROPS)?;
g.link(20, 30, FOLLOWS, NO_PROPS)?;

let s = Snapshot::of(&g);
// Ids are renumbered from zero, in the order the graph's own ids sort.
assert_eq!(s.nodes(), 3);
assert_eq!(s.dense(20), Some(1));
assert_eq!(s.out(1), [2]);
assert_eq!(s.into_(1), [0]);
assert_eq!(s.id(2), 30);

§Why the numbering is sorted and not arrival order

Because it has to be reproducible. Two snapshots of the same graph have to give the same dense id to the same node, or a caller cannot hold a PageRank vector from one run and a component table from another and compare them by index. The property store hands its ids back in whatever order its table happens to hold them, so the ids are sorted, and sorting is also the cheapest way to build the reverse map for the case that matters.

§Two ways to turn a graph’s id into a dense one

A graph whose ids came out of a counter, which is every graph the wire path builds, has ids that are already 0..n. That gets a direct table: one Vec<u32> indexed by the id, one load per lookup, no comparisons. A graph whose ids are hashes or timestamps gets a binary search over the sorted ids, which is about twenty dependent loads instead of one.

The rule is the table when the highest id is less than twice the node count, so the table is never more than twice the size of the ids it replaces, and the choice is made once when the snapshot is built rather than per lookup. It only matters while the snapshot is being built, because after that every algorithm works in dense ids and never asks.

§The reverse index is transposed and not read

The plane can index incoming edges and this does not use that. The incoming CSR here is the transpose of the outgoing one, which is a counting sort over the edges that were projected, because that is the only way the two can be guaranteed to be the same set of edges. It also means an algorithm that needs predecessors, which is most of the interesting ones, works on a graph built with crate::Graph::out_only.

Structs§

Snapshot
A graph as dense arrays: ids renumbered from zero, one CSR per direction.