Use non-zero edge indices to reduce size of structs with Option fields

This commit is contained in:
2026-09-03 20:28:41 +02:00
parent c25a8a4fc1
commit 1209d75e15
2 changed files with 44 additions and 22 deletions
+18 -7
View File
@@ -1,5 +1,6 @@
//! [`Graph`], an undirected graph topology supporting addition and deletion.
use std::num::NonZeroUsize;
use typed_generational_arena::{Arena, Index};
use crate::maps::ElementMap;
@@ -31,10 +32,20 @@ pub type GraphIncidence = Incidence<Vertex, Edge>;
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
struct VertexSlot(usize);
// TODO: Benchmark the cost of the index shift. An alternative could be to init Graph::incidences with two dummy entries to avoid 0.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
struct IncidenceSlot(usize);
struct IncidenceSlot(NonZeroUsize);
impl IncidenceSlot {
fn new(index: usize) -> Self {
IncidenceSlot(NonZeroUsize::new(index + 1).unwrap())
}
fn index(self) -> usize {
self.0.get() - 1
}
}
// TODO: Check if VertexIncidenceHeader and IncidenceEntry can be made smaller. Currently they both take 24 bytes (on 64bit), see https://stackoverflow.com/a/79653173
/// `pub` because [`Vertex`] references it as a type parameter of the underlying arena. Not
/// intended for direct external use.
#[doc(hidden)]
@@ -147,7 +158,7 @@ impl Graph {
adjacent: VertexSlot(v2.arr_idx()),
});
self.vertices[v1].incidence_count += 1;
self.vertices[v1].first_incidence = Some(IncidenceSlot(edge.arr_idx()));
self.vertices[v1].first_incidence = Some(IncidenceSlot::new(edge.arr_idx()));
edge
}
@@ -186,7 +197,7 @@ impl Graph {
let first = vertex_header
.first_incidence
.expect("incident vertex without incidences, corrupt internal data state");
if first.0 == e.arr_idx() {
if first.index() == e.arr_idx() {
vertex_header.first_incidence = next;
} else {
let graph: &Graph = self;
@@ -195,7 +206,7 @@ impl Graph {
.find(|(_, f)| {
graph.incidences[*f]
.next
.is_some_and(|i| i.0 == e.arr_idx())
.is_some_and(|i| i.index() == e.arr_idx())
})
.expect("cannot find previous incidence, corrupt internal data state");
self.incidences[previous].next = next;
@@ -210,7 +221,7 @@ impl Graph {
fn step_incidence(&self, incidence: &mut Option<IncidenceSlot>) -> Option<(VertexSlot, Edge)> {
// TODO: Benchmark storing full Index (one read, larger entries) vs. slot + get_idx() (two reads, smaller entries).
let current = (*incidence)?;
let e = self.incidences.get_idx(current.0).unwrap();
let e = self.incidences.get_idx(current.index()).unwrap();
let entry = self.incidences[e];
*incidence = entry.next;
Some((entry.adjacent, e))
@@ -371,7 +382,7 @@ impl GraphTopologyDeletion for Graph {
if e_entry.adjacent != f_entry.adjacent {
self.update_incidence_list(e, f_entry.adjacent, e_entry.next, false);
self.update_incidence_list(f, e_entry.adjacent, f_entry.next, false);
} else if f_entry.next.is_some_and(|i| e.arr_idx() == i.0) {
} else if f_entry.next.is_some_and(|i| e.arr_idx() == i.index()) {
self.update_incidence_list(f, e_entry.adjacent, e_entry.next, true);
} else {
self.update_incidence_list(e, e_entry.adjacent, f_entry.next, true);