use yo_common::prefetch;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Dir {
Out,
In,
}
const CLASSES: usize = 96;
const LADDER: [u32; CLASSES] = ladder();
const LIVE: u8 = 1;
const INCOMING: u8 = 2;
const fn ladder() -> [u32; CLASSES] {
let mut out = [0u32; CLASSES];
let mut cap: u64 = 1;
let mut i = 0;
while i < CLASSES {
out[i] = if cap > u32::MAX as u64 {
u32::MAX
} else {
cap as u32
};
cap = if cap < 16 {
cap * 2
} else {
(cap + cap / 4 + 3) & !3
};
i += 1;
}
out
}
#[derive(Debug, Clone, Copy, Default)]
struct Slot {
node: u64,
at: u32,
len: u32,
cap: u32,
label: u32,
flags: u8,
}
#[derive(Debug)]
pub struct Adjacency {
slots: Vec<Slot>,
live: usize,
filled: usize,
edges: usize,
entries: usize,
neighbour: Vec<u64>,
edge: Vec<u32>,
free: Vec<Vec<u32>>,
both: bool,
}
impl Default for Adjacency {
fn default() -> Adjacency {
Adjacency::new()
}
}
impl Adjacency {
#[must_use]
pub fn new() -> Adjacency {
Adjacency::build(true)
}
#[must_use]
pub fn out_only() -> Adjacency {
Adjacency::build(false)
}
fn build(both: bool) -> Adjacency {
Adjacency {
slots: Vec::new(),
live: 0,
filled: 0,
edges: 0,
entries: 0,
neighbour: Vec::new(),
edge: Vec::new(),
free: Vec::new(),
both,
}
}
#[must_use]
pub fn indexes_incoming(&self) -> bool {
self.both
}
#[must_use]
pub fn edges(&self) -> usize {
self.edges
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.edges == 0
}
#[must_use]
pub fn runs(&self) -> usize {
self.filled
}
pub fn link(&mut self, src: u64, dst: u64, label: u32, edge: u32) {
let s = self.run_for(src, label, 0);
self.push(s, dst, edge);
if self.both {
let d = self.run_for(dst, label, INCOMING);
self.push(d, src, edge);
}
self.edges += 1;
}
pub fn unlink(&mut self, src: u64, dst: u64, label: u32) -> Option<u32> {
let s = self.find(src, label, 0)?;
let i = self.position(s, dst)?;
let edge = self.take(s, i).1;
if self.both
&& let Some(d) = self.find(dst, label, INCOMING)
&& let Some(j) = self.position(d, src)
{
self.take(d, j);
}
self.edges -= 1;
Some(edge)
}
pub fn unlink_at(&mut self, node: u64, label: u32, dir: Dir, i: usize) -> Option<(u64, u32)> {
let s = self.find(node, label, incoming(dir))?;
if i >= self.slots[s].len as usize {
return None;
}
Some(self.take(s, i))
}
#[must_use]
pub fn neighbours(&self, node: u64, label: u32, dir: Dir) -> &[u64] {
match self.find(node, label, incoming(dir)) {
Some(s) => {
let (at, len) = (self.slots[s].at as usize, self.slots[s].len as usize);
&self.neighbour[at..at + len]
}
None => &[],
}
}
#[must_use]
pub fn edge_slots(&self, node: u64, label: u32, dir: Dir) -> &[u32] {
match self.find(node, label, incoming(dir)) {
Some(s) => {
let (at, len) = (self.slots[s].at as usize, self.slots[s].len as usize);
&self.edge[at..at + len]
}
None => &[],
}
}
#[must_use]
pub fn degree(&self, node: u64, label: u32, dir: Dir) -> usize {
match self.find(node, label, incoming(dir)) {
Some(s) => self.slots[s].len as usize,
None => 0,
}
}
pub fn for_each_run(&self, label: u32, dir: Dir, mut f: impl FnMut(u64, &[u64], &[u32])) {
let want = incoming(dir);
for s in &self.slots {
if s.flags & LIVE == 0 || s.len == 0 || s.label != label || s.flags & INCOMING != want {
continue;
}
let (at, len) = (s.at as usize, s.len as usize);
f(
s.node,
&self.neighbour[at..at + len],
&self.edge[at..at + len],
);
}
}
pub fn prefetch(&self, node: u64, label: u32, dir: Dir) {
if self.slots.is_empty() {
return;
}
let i = bucket(hash(node, label, incoming(dir)), self.slots.len());
prefetch(&self.slots[i]);
}
#[must_use]
pub fn bytes(&self) -> usize {
self.slots.capacity() * size_of::<Slot>()
+ self.neighbour.capacity() * size_of::<u64>()
+ self.edge.capacity() * size_of::<u32>()
+ self.free.capacity() * size_of::<Vec<u32>>()
+ self
.free
.iter()
.map(|f| f.capacity() * size_of::<u32>())
.sum::<usize>()
}
pub fn compact(&mut self) {
let mut keep: Vec<Slot> = self
.slots
.iter()
.copied()
.filter(|s| s.flags & LIVE != 0 && s.len > 0)
.collect();
let mut neighbour = Vec::with_capacity(self.entries);
let mut edge = Vec::with_capacity(self.entries);
for slot in &mut keep {
let (at, len) = (slot.at as usize, slot.len as usize);
let to = neighbour.len() as u32;
neighbour.extend_from_slice(&self.neighbour[at..at + len]);
edge.extend_from_slice(&self.edge[at..at + len]);
slot.at = to;
slot.cap = slot.len;
}
self.neighbour = neighbour;
self.edge = edge;
self.free = Vec::new();
self.live = keep.len();
self.filled = keep.len();
self.slots = vec![Slot::default(); (keep.len() * 4 / 3).max(16)];
for slot in keep {
let i = self.vacancy(slot.node, slot.label, slot.flags & INCOMING);
self.slots[i] = slot;
}
}
fn find(&self, node: u64, label: u32, incoming: u8) -> Option<usize> {
if self.slots.is_empty() {
return None;
}
let n = self.slots.len();
let mut i = bucket(hash(node, label, incoming), n);
loop {
let s = &self.slots[i];
if s.flags & LIVE == 0 {
return None;
}
if s.node == node && s.label == label && s.flags & INCOMING == incoming {
return Some(i);
}
i += 1;
if i == n {
i = 0;
}
}
}
fn position(&self, s: usize, node: u64) -> Option<usize> {
let (at, len) = (self.slots[s].at as usize, self.slots[s].len as usize);
self.neighbour[at..at + len].iter().position(|n| *n == node)
}
fn run_for(&mut self, node: u64, label: u32, incoming: u8) -> usize {
if (self.live + 1) * 4 > self.slots.len() * 3 {
self.regrow();
}
let n = self.slots.len();
let mut i = bucket(hash(node, label, incoming), n);
loop {
let s = &self.slots[i];
if s.flags & LIVE == 0 {
self.slots[i] = Slot {
node,
at: 0,
len: 0,
cap: 0,
label,
flags: LIVE | incoming,
};
self.live += 1;
return i;
}
if s.node == node && s.label == label && s.flags & INCOMING == incoming {
return i;
}
i += 1;
if i == n {
i = 0;
}
}
}
fn vacancy(&self, node: u64, label: u32, incoming: u8) -> usize {
let n = self.slots.len();
let mut i = bucket(hash(node, label, incoming), n);
while self.slots[i].flags & LIVE != 0 {
i += 1;
if i == n {
i = 0;
}
}
i
}
fn regrow(&mut self) {
let want = (self.slots.len() + self.slots.len() / 4).max(16);
let old = core::mem::replace(&mut self.slots, vec![Slot::default(); want]);
for slot in old {
if slot.flags & LIVE != 0 {
let i = self.vacancy(slot.node, slot.label, slot.flags & INCOMING);
self.slots[i] = slot;
}
}
}
fn push(&mut self, s: usize, node: u64, edge: u32) {
let Slot {
mut at,
len,
mut cap,
..
} = self.slots[s];
if len == cap {
let want = LADDER[ceil_class(cap + 1)];
let to = self.alloc(want);
if len > 0 {
self.copy_run(at, to, len as usize);
self.release(at, cap);
}
at = to;
cap = want;
}
let i = at as usize + len as usize;
self.neighbour[i] = node;
self.edge[i] = edge;
self.slots[s].at = at;
self.slots[s].cap = cap;
self.slots[s].len = len + 1;
self.entries += 1;
if len == 0 {
self.filled += 1;
}
}
fn take(&mut self, s: usize, i: usize) -> (u64, u32) {
let Slot { at, len, cap, .. } = self.slots[s];
let (at, last) = (at as usize, at as usize + len as usize - 1);
let gone = (self.neighbour[at + i], self.edge[at + i]);
self.neighbour[at + i] = self.neighbour[last];
self.edge[at + i] = self.edge[last];
self.slots[s].len = len - 1;
self.entries -= 1;
if len == 1 {
self.filled -= 1;
}
self.shrink(s, cap);
gone
}
fn shrink(&mut self, s: usize, cap: u32) {
let len = self.slots[s].len;
if len == 0 {
self.release(self.slots[s].at, cap);
self.slots[s].at = 0;
self.slots[s].cap = 0;
return;
}
if len * 2 > cap {
return;
}
let want = LADDER[ceil_class(len)];
if want >= cap {
return;
}
let at = self.slots[s].at;
let to = self.alloc(want);
self.copy_run(at, to, len as usize);
self.release(at, cap);
self.slots[s].at = to;
self.slots[s].cap = want;
}
fn copy_run(&mut self, from: u32, to: u32, len: usize) {
let (from, to) = (from as usize, to as usize);
self.neighbour.copy_within(from..from + len, to);
self.edge.copy_within(from..from + len, to);
}
fn alloc(&mut self, cap: u32) -> u32 {
let class = ceil_class(cap);
if let Some(list) = self.free.get_mut(class)
&& let Some(at) = list.pop()
{
return at;
}
let cap = cap as usize;
let at = self.neighbour.len();
assert!(at + cap <= u32::MAX as usize, "the adjacency arena is full");
if at + cap > self.neighbour.capacity() {
let cur = self.neighbour.capacity();
let want = (cur + cur / 8).max(at + cap).max(64);
self.neighbour.reserve_exact(want - at);
self.edge.reserve_exact(want - at);
}
self.neighbour.resize(at + cap, 0);
self.edge.resize(at + cap, 0);
at as u32
}
fn release(&mut self, at: u32, cap: u32) {
let class = floor_class(cap);
while self.free.len() <= class {
self.free.push(Vec::new());
}
self.free[class].push(at);
}
}
fn ceil_class(n: u32) -> usize {
LADDER.iter().position(|c| *c >= n).unwrap_or(CLASSES - 1)
}
fn floor_class(n: u32) -> usize {
let at = ceil_class(n);
if LADDER[at] > n {
at.saturating_sub(1)
} else {
at
}
}
fn incoming(dir: Dir) -> u8 {
match dir {
Dir::Out => 0,
Dir::In => INCOMING,
}
}
#[inline]
fn bucket(h: u64, n: usize) -> usize {
((u128::from(h) * n as u128) >> 64) as usize
}
#[inline]
fn hash(node: u64, label: u32, incoming: u8) -> u64 {
let tag = (u64::from(label) << 1) | u64::from(incoming >> 1);
let mut x = node ^ tag.wrapping_mul(0x9e37_79b9_7f4a_7c15);
x ^= x >> 33;
x = x.wrapping_mul(0xff51_afd7_ed55_8ccd);
x ^= x >> 29;
x = x.wrapping_mul(0xc4ce_b9fe_1a85_ec53);
x ^ (x >> 32)
}
#[cfg(test)]
mod tests {
use super::*;
use yo_common::Rng;
const FOLLOWS: u32 = 1;
const WORKS_AT: u32 = 2;
fn sorted(v: &[u64]) -> Vec<u64> {
let mut v = v.to_vec();
v.sort_unstable();
v
}
#[test]
fn a_slot_is_one_half_of_a_cache_line() {
assert_eq!(size_of::<Slot>(), 32);
}
#[test]
fn the_ladder_only_ever_goes_up() {
for w in LADDER.windows(2) {
assert!(w[1] > w[0] || w[0] == u32::MAX, "{w:?} does not go up");
}
assert_eq!(LADDER[4], 16, "doubling should run out at 16");
assert_eq!(
LADDER[5], 20,
"and a quarter more should be the step after it"
);
assert!(
LADDER.contains(&u32::MAX),
"the ladder should reach the end of a u32"
);
assert_eq!(LADDER[ceil_class(17)], 20);
assert_eq!(LADDER[floor_class(19)], 16);
assert_eq!(LADDER[floor_class(20)], 20);
assert_eq!(LADDER[ceil_class(1)], 1);
}
#[test]
fn a_run_is_the_neighbours_that_were_linked_to_it() {
let mut g = Adjacency::new();
for (i, dst) in [7u64, 9, 11].iter().enumerate() {
g.link(1, *dst, FOLLOWS, i as u32);
}
assert_eq!(g.neighbours(1, FOLLOWS, Dir::Out), &[7, 9, 11]);
assert_eq!(g.edge_slots(1, FOLLOWS, Dir::Out), &[0, 1, 2]);
assert_eq!(g.degree(1, FOLLOWS, Dir::Out), 3);
assert_eq!(g.edges(), 3);
assert_eq!(g.runs(), 4);
}
#[test]
fn a_node_nobody_linked_has_no_neighbours_rather_than_no_answer() {
let g = Adjacency::new();
assert!(g.neighbours(1, FOLLOWS, Dir::Out).is_empty());
assert!(g.edge_slots(1, FOLLOWS, Dir::Out).is_empty());
assert_eq!(g.degree(1, FOLLOWS, Dir::Out), 0);
assert!(g.is_empty());
g.prefetch(1, FOLLOWS, Dir::Out);
}
#[test]
fn an_edge_is_readable_from_both_ends() {
let mut g = Adjacency::new();
g.link(1, 2, FOLLOWS, 10);
assert_eq!(g.neighbours(1, FOLLOWS, Dir::Out), &[2]);
assert_eq!(g.neighbours(2, FOLLOWS, Dir::In), &[1]);
assert_eq!(g.edge_slots(2, FOLLOWS, Dir::In), &[10]);
assert!(g.neighbours(2, FOLLOWS, Dir::Out).is_empty());
}
#[test]
fn one_label_is_not_another() {
let mut g = Adjacency::new();
g.link(1, 2, FOLLOWS, 10);
g.link(1, 3, WORKS_AT, 11);
assert_eq!(g.neighbours(1, FOLLOWS, Dir::Out), &[2]);
assert_eq!(g.neighbours(1, WORKS_AT, Dir::Out), &[3]);
}
#[test]
fn a_self_loop_is_at_both_of_its_ends_and_they_are_different_runs() {
let mut g = Adjacency::new();
g.link(1, 1, FOLLOWS, 5);
assert_eq!(g.neighbours(1, FOLLOWS, Dir::Out), &[1]);
assert_eq!(g.neighbours(1, FOLLOWS, Dir::In), &[1]);
assert_eq!(g.unlink(1, 1, FOLLOWS), Some(5));
assert!(g.neighbours(1, FOLLOWS, Dir::Out).is_empty());
assert!(g.neighbours(1, FOLLOWS, Dir::In).is_empty());
}
#[test]
fn out_only_stores_nothing_incoming() {
let mut both = Adjacency::new();
let mut out = Adjacency::out_only();
for i in 0..1000u64 {
both.link(i, i + 1, FOLLOWS, i as u32);
out.link(i, i + 1, FOLLOWS, i as u32);
}
assert_eq!(out.neighbours(500, FOLLOWS, Dir::Out), &[501]);
assert!(out.neighbours(500, FOLLOWS, Dir::In).is_empty());
assert_eq!(both.neighbours(500, FOLLOWS, Dir::In), &[499]);
assert!(!out.indexes_incoming());
assert!(
out.bytes() * 3 < both.bytes() * 2,
"{} against {}",
out.bytes(),
both.bytes()
);
}
#[test]
fn unlinking_takes_the_edge_off_both_ends() {
let mut g = Adjacency::new();
g.link(1, 2, FOLLOWS, 10);
g.link(1, 3, FOLLOWS, 11);
assert_eq!(g.unlink(1, 2, FOLLOWS), Some(10));
assert_eq!(g.neighbours(1, FOLLOWS, Dir::Out), &[3]);
assert!(g.neighbours(2, FOLLOWS, Dir::In).is_empty());
assert_eq!(g.edges(), 1);
assert_eq!(g.unlink(1, 2, FOLLOWS), None);
assert_eq!(g.unlink(9, 9, FOLLOWS), None);
}
#[test]
fn a_delete_moves_the_last_edge_into_the_hole() {
let mut g = Adjacency::new();
for dst in 1..=5u64 {
g.link(0, dst, FOLLOWS, dst as u32);
}
g.unlink(0, 2, FOLLOWS);
let n = g.neighbours(0, FOLLOWS, Dir::Out);
let e = g.edge_slots(0, FOLLOWS, Dir::Out);
assert_eq!(sorted(n), vec![1, 3, 4, 5]);
for (i, node) in n.iter().enumerate() {
assert_eq!(u64::from(e[i]), *node, "the pairing survived the swap");
}
}
#[test]
fn unlink_at_moves_the_last_entry_into_the_position_it_took() {
let mut g = Adjacency::new();
for dst in 1..=4u64 {
g.link(0, dst, FOLLOWS, dst as u32);
}
assert_eq!(g.unlink_at(0, FOLLOWS, Dir::Out, 0), Some((1, 1)));
assert_eq!(g.neighbours(0, FOLLOWS, Dir::Out), &[4, 2, 3]);
assert_eq!(g.unlink_at(0, FOLLOWS, Dir::Out, 9), None);
assert_eq!(g.unlink_at(7, FOLLOWS, Dir::Out, 0), None);
}
#[test]
fn a_run_survives_growing_through_every_size_it_passes() {
let mut g = Adjacency::out_only();
let n = 5000u64;
for dst in 0..n {
g.link(0, dst, FOLLOWS, dst as u32);
}
assert_eq!(g.degree(0, FOLLOWS, Dir::Out), n as usize);
assert_eq!(
g.neighbours(0, FOLLOWS, Dir::Out),
(0..n).collect::<Vec<_>>()
);
assert_eq!(
g.edge_slots(0, FOLLOWS, Dir::Out),
(0..n as u32).collect::<Vec<_>>()
);
}
#[test]
fn a_hub_that_empties_gives_its_block_back() {
let mut g = Adjacency::out_only();
for dst in 0..4000u64 {
g.link(0, dst, FOLLOWS, 0);
}
let full = g.bytes();
for dst in 0..4000u64 {
assert!(g.unlink(0, dst, FOLLOWS).is_some());
}
assert_eq!(g.degree(0, FOLLOWS, Dir::Out), 0);
assert_eq!(g.runs(), 0);
for dst in 0..4000u64 {
g.link(1, dst, FOLLOWS, 0);
}
assert!(g.bytes() <= full + full / 4, "{} against {full}", g.bytes());
}
#[test]
fn a_run_that_grows_and_shrinks_does_not_copy_itself_on_a_boundary() {
let mut g = Adjacency::out_only();
for dst in 0..16u64 {
g.link(0, dst, FOLLOWS, 0);
}
g.link(0, 999, FOLLOWS, 0);
g.unlink(0, 999, FOLLOWS);
let settled = g.bytes();
for _ in 0..100 {
g.link(0, 999, FOLLOWS, 0);
g.unlink(0, 999, FOLLOWS);
}
assert_eq!(g.bytes(), settled);
assert_eq!(g.degree(0, FOLLOWS, Dir::Out), 16);
}
#[test]
fn a_run_that_loses_most_of_itself_gives_the_room_back() {
let mut g = Adjacency::out_only();
for dst in 0..4000u64 {
g.link(0, dst, FOLLOWS, 0);
}
for dst in 0..3990u64 {
g.unlink(0, dst, FOLLOWS);
}
assert_eq!(g.degree(0, FOLLOWS, Dir::Out), 10);
assert!(g.slots.iter().any(|s| s.len == 10 && s.cap <= 16));
g.compact();
assert_eq!(g.degree(0, FOLLOWS, Dir::Out), 10);
assert!(g.bytes() < 4000, "{} bytes for ten edges", g.bytes());
}
#[test]
fn compact_drops_the_runs_that_emptied() {
let mut g = Adjacency::new();
for i in 0..2000u64 {
g.link(i, i + 1, FOLLOWS, i as u32);
}
for i in 0..1990u64 {
g.unlink(i, i + 1, FOLLOWS);
}
assert_eq!(g.runs(), 20);
let before = g.bytes();
g.compact();
assert_eq!(g.runs(), 20);
assert_eq!(g.edges(), 10);
assert_eq!(g.neighbours(1995, FOLLOWS, Dir::Out), &[1996]);
assert_eq!(g.neighbours(1996, FOLLOWS, Dir::In), &[1995]);
assert!(g.bytes() * 4 < before, "{} against {before}", g.bytes());
g.link(1995, 3000, FOLLOWS, 7);
assert_eq!(
sorted(g.neighbours(1995, FOLLOWS, Dir::Out)),
vec![1996, 3000]
);
assert_eq!(sorted(g.neighbours(3000, FOLLOWS, Dir::In)), vec![1995]);
}
#[test]
fn a_hot_run_costs_about_twelve_bytes_an_edge() {
let mut g = Adjacency::out_only();
let mut rng = Rng::new(0x9e3f);
let nodes = 200_000u64;
let mut edges = 0usize;
for src in 0..nodes {
let deg = match rng.next_u64() % 1000 {
0..=799 => 1 + rng.next_u64() % 4,
800..=979 => 5 + rng.next_u64() % 40,
_ => 45 + rng.next_u64() % 600,
};
for _ in 0..deg {
g.link(src, rng.next_u64() % nodes, FOLLOWS, 0);
edges += 1;
}
}
let per = g.bytes() as f64 / edges as f64;
g.compact();
let settled = g.bytes() as f64 / edges as f64;
assert!(per < 19.0, "{per:.2} bytes an edge over {edges} edges");
assert!(settled < 16.0, "{settled:.2} bytes an edge once swept");
}
#[test]
fn a_two_hop_reaches_what_a_pair_of_one_hops_reaches() {
let mut g = Adjacency::new();
let mut rng = Rng::new(7);
let nodes = 5000u64;
for src in 0..nodes {
for _ in 0..8 {
g.link(src, rng.next_u64() % nodes, FOLLOWS, 0);
}
}
let first = g.neighbours(0, FOLLOWS, Dir::Out).to_vec();
for hop in &first {
g.prefetch(*hop, FOLLOWS, Dir::Out);
}
let mut seen = Vec::new();
for hop in &first {
seen.extend_from_slice(g.neighbours(*hop, FOLLOWS, Dir::Out));
}
assert_eq!(seen.len(), 64);
for (i, hop) in first.iter().enumerate() {
for dst in &seen[i * 8..(i + 1) * 8] {
assert!(g.neighbours(*dst, FOLLOWS, Dir::In).contains(hop));
}
}
}
#[test]
fn the_plane_agrees_with_a_list_of_what_was_done_to_it() {
let mut g = Adjacency::new();
let mut want: Vec<Vec<u64>> = vec![Vec::new(); 64];
let mut rng = Rng::new(0xbeef);
for _ in 0..200_000 {
let src = rng.next_u64() % 64;
let dst = rng.next_u64() % 64;
if rng.next_u64().is_multiple_of(3) {
if let Some(i) = want[src as usize].iter().position(|n| *n == dst) {
want[src as usize].swap_remove(i);
assert!(g.unlink(src, dst, FOLLOWS).is_some());
} else {
assert_eq!(g.unlink(src, dst, FOLLOWS), None);
}
} else {
want[src as usize].push(dst);
g.link(src, dst, FOLLOWS, 0);
}
}
let mut total = 0;
for (src, list) in want.iter().enumerate() {
assert_eq!(
sorted(g.neighbours(src as u64, FOLLOWS, Dir::Out)),
sorted(list),
"node {src}"
);
total += list.len();
}
assert_eq!(g.edges(), total);
let mut incoming: Vec<Vec<u64>> = vec![Vec::new(); 64];
for (src, list) in want.iter().enumerate() {
for dst in list {
incoming[*dst as usize].push(src as u64);
}
}
for (dst, list) in incoming.iter().enumerate() {
assert_eq!(
sorted(g.neighbours(dst as u64, FOLLOWS, Dir::In)),
sorted(list),
"into node {dst}"
);
}
}
}