Use non-zero edge indices to reduce size of structs with Option fields
This commit is contained in:
+26
-15
@@ -1,5 +1,7 @@
|
|||||||
//! [`AppendGraph`], an undirected graph topology supporting addition only.
|
//! [`AppendGraph`], an undirected graph topology supporting addition only.
|
||||||
|
|
||||||
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
use crate::maps::ElementMap;
|
use crate::maps::ElementMap;
|
||||||
use crate::traits::{GraphTopology, GraphTopologyAddition, Incidence, IncidenceCursor};
|
use crate::traits::{GraphTopology, GraphTopologyAddition, Incidence, IncidenceCursor};
|
||||||
|
|
||||||
@@ -10,12 +12,27 @@ use crate::traits::{GraphTopology, GraphTopologyAddition, Incidence, IncidenceCu
|
|||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
||||||
pub struct Vertex(usize);
|
pub struct Vertex(usize);
|
||||||
|
|
||||||
|
// TODO: Benchmark the cost of the index shift. An alternative could be to init AppendGraph::incidences with two dummy entries to avoid 0.
|
||||||
/// An opaque handle identifying an edge in an [`AppendGraph`].
|
/// An opaque handle identifying an edge in an [`AppendGraph`].
|
||||||
///
|
///
|
||||||
/// Handles are stable for the lifetime of the graph. Obtain via graph methods like
|
/// Handles are stable for the lifetime of the graph. Obtain via graph methods like
|
||||||
/// [`AppendGraph::add_edge`] and [`AppendGraph::edges`].
|
/// [`AppendGraph::add_edge`] and [`AppendGraph::edges`].
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
||||||
pub struct Edge(usize);
|
pub struct Edge(NonZeroUsize);
|
||||||
|
|
||||||
|
impl Edge {
|
||||||
|
fn new(index: usize) -> Self {
|
||||||
|
Edge(NonZeroUsize::new(index + 1).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index(self) -> usize {
|
||||||
|
self.0.get() - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize(&self) -> Self {
|
||||||
|
Self::new(self.index() & !1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An [`ElementMap`] for [`AppendGraph`] vertices.
|
/// An [`ElementMap`] for [`AppendGraph`] vertices.
|
||||||
pub type AppendGraphVertexMap<T> = ElementMap<Vertex, T>;
|
pub type AppendGraphVertexMap<T> = ElementMap<Vertex, T>;
|
||||||
@@ -26,13 +43,6 @@ pub type AppendGraphEdgeMap<T> = ElementMap<Edge, T>;
|
|||||||
/// An [`Incidence`] for [`AppendGraph`].
|
/// An [`Incidence`] for [`AppendGraph`].
|
||||||
pub type AppendGraphIncidence = Incidence<Vertex, Edge>;
|
pub type AppendGraphIncidence = Incidence<Vertex, Edge>;
|
||||||
|
|
||||||
impl Edge {
|
|
||||||
fn normalize(&self) -> Self {
|
|
||||||
Self(self.0 & !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
|
|
||||||
struct VertexIncidenceHeader {
|
struct VertexIncidenceHeader {
|
||||||
incidence_count: usize,
|
incidence_count: usize,
|
||||||
first_incidence: Option<Edge>,
|
first_incidence: Option<Edge>,
|
||||||
@@ -118,7 +128,7 @@ impl AppendGraph {
|
|||||||
adjacent: v2,
|
adjacent: v2,
|
||||||
});
|
});
|
||||||
self.vertices[v1.0].incidence_count += 1;
|
self.vertices[v1.0].incidence_count += 1;
|
||||||
self.vertices[v1.0].first_incidence = Some(Edge(self.incidences.len() - 1));
|
self.vertices[v1.0].first_incidence = Some(Edge::new(self.incidences.len() - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn raw_incidences(&self, v: Vertex) -> impl Iterator<Item = (Vertex, Edge)> {
|
fn raw_incidences(&self, v: Vertex) -> impl Iterator<Item = (Vertex, Edge)> {
|
||||||
@@ -128,7 +138,7 @@ impl AppendGraph {
|
|||||||
|
|
||||||
fn step_incidence(&self, incidence: &mut Option<Edge>) -> Option<(Vertex, Edge)> {
|
fn step_incidence(&self, incidence: &mut Option<Edge>) -> Option<(Vertex, Edge)> {
|
||||||
let current = (*incidence)?;
|
let current = (*incidence)?;
|
||||||
let entry = self.incidences[current.0];
|
let entry = self.incidences[current.index()];
|
||||||
*incidence = entry.next;
|
*incidence = entry.next;
|
||||||
Some((entry.adjacent, current))
|
Some((entry.adjacent, current))
|
||||||
}
|
}
|
||||||
@@ -160,7 +170,7 @@ impl GraphTopology for AppendGraph {
|
|||||||
|
|
||||||
// TODO: Create with capacity 0, maybe offer pre-allocated alternative?
|
// TODO: Create with capacity 0, maybe offer pre-allocated alternative?
|
||||||
fn edge_map<T: Clone>(&self, default: T) -> ElementMap<Self::Edge, T> {
|
fn edge_map<T: Clone>(&self, default: T) -> ElementMap<Self::Edge, T> {
|
||||||
ElementMap::new(default, |e| e.0 / 2, self.edge_capacity())
|
ElementMap::new(default, |e| e.index() / 2, self.edge_capacity())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn degree(&self, v: Self::Vertex) -> usize {
|
fn degree(&self, v: Self::Vertex) -> usize {
|
||||||
@@ -180,14 +190,15 @@ impl GraphTopology for AppendGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn incident_vertices(&self, e: Self::Edge) -> (Self::Vertex, Self::Vertex) {
|
fn incident_vertices(&self, e: Self::Edge) -> (Self::Vertex, Self::Vertex) {
|
||||||
|
let ei = e.index();
|
||||||
(
|
(
|
||||||
self.incidences[e.0 ^ 1].adjacent,
|
self.incidences[ei ^ 1].adjacent,
|
||||||
self.incidences[e.0].adjacent,
|
self.incidences[ei].adjacent,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn edges(&self) -> impl Iterator<Item = Self::Edge> {
|
fn edges(&self) -> impl Iterator<Item = Self::Edge> {
|
||||||
(0..self.incidences.len()).step_by(2).map(Edge)
|
(0..self.incidences.len()).step_by(2).map(Edge::new)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn incident_edges(&self, v: Self::Vertex) -> impl Iterator<Item = Self::Edge> {
|
fn incident_edges(&self, v: Self::Vertex) -> impl Iterator<Item = Self::Edge> {
|
||||||
@@ -236,7 +247,7 @@ impl GraphTopologyAddition for AppendGraph {
|
|||||||
fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) -> Self::Edge {
|
fn add_edge(&mut self, v1: Self::Vertex, v2: Self::Vertex) -> Self::Edge {
|
||||||
self.add_incidence(v1, v2);
|
self.add_incidence(v1, v2);
|
||||||
self.add_incidence(v2, v1);
|
self.add_incidence(v2, v1);
|
||||||
Edge(self.incidences.len() - 2)
|
Edge::new(self.incidences.len() - 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+18
-7
@@ -1,5 +1,6 @@
|
|||||||
//! [`Graph`], an undirected graph topology supporting addition and deletion.
|
//! [`Graph`], an undirected graph topology supporting addition and deletion.
|
||||||
|
|
||||||
|
use std::num::NonZeroUsize;
|
||||||
use typed_generational_arena::{Arena, Index};
|
use typed_generational_arena::{Arena, Index};
|
||||||
|
|
||||||
use crate::maps::ElementMap;
|
use crate::maps::ElementMap;
|
||||||
@@ -31,10 +32,20 @@ pub type GraphIncidence = Incidence<Vertex, Edge>;
|
|||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
struct VertexSlot(usize);
|
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)]
|
#[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
|
/// `pub` because [`Vertex`] references it as a type parameter of the underlying arena. Not
|
||||||
/// intended for direct external use.
|
/// intended for direct external use.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
@@ -147,7 +158,7 @@ impl Graph {
|
|||||||
adjacent: VertexSlot(v2.arr_idx()),
|
adjacent: VertexSlot(v2.arr_idx()),
|
||||||
});
|
});
|
||||||
self.vertices[v1].incidence_count += 1;
|
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
|
edge
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +197,7 @@ impl Graph {
|
|||||||
let first = vertex_header
|
let first = vertex_header
|
||||||
.first_incidence
|
.first_incidence
|
||||||
.expect("incident vertex without incidences, corrupt internal data state");
|
.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;
|
vertex_header.first_incidence = next;
|
||||||
} else {
|
} else {
|
||||||
let graph: &Graph = self;
|
let graph: &Graph = self;
|
||||||
@@ -195,7 +206,7 @@ impl Graph {
|
|||||||
.find(|(_, f)| {
|
.find(|(_, f)| {
|
||||||
graph.incidences[*f]
|
graph.incidences[*f]
|
||||||
.next
|
.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");
|
.expect("cannot find previous incidence, corrupt internal data state");
|
||||||
self.incidences[previous].next = next;
|
self.incidences[previous].next = next;
|
||||||
@@ -210,7 +221,7 @@ impl Graph {
|
|||||||
fn step_incidence(&self, incidence: &mut Option<IncidenceSlot>) -> Option<(VertexSlot, Edge)> {
|
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).
|
// TODO: Benchmark storing full Index (one read, larger entries) vs. slot + get_idx() (two reads, smaller entries).
|
||||||
let current = (*incidence)?;
|
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];
|
let entry = self.incidences[e];
|
||||||
*incidence = entry.next;
|
*incidence = entry.next;
|
||||||
Some((entry.adjacent, e))
|
Some((entry.adjacent, e))
|
||||||
@@ -371,7 +382,7 @@ impl GraphTopologyDeletion for Graph {
|
|||||||
if e_entry.adjacent != f_entry.adjacent {
|
if e_entry.adjacent != f_entry.adjacent {
|
||||||
self.update_incidence_list(e, f_entry.adjacent, e_entry.next, false);
|
self.update_incidence_list(e, f_entry.adjacent, e_entry.next, false);
|
||||||
self.update_incidence_list(f, e_entry.adjacent, f_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);
|
self.update_incidence_list(f, e_entry.adjacent, e_entry.next, true);
|
||||||
} else {
|
} else {
|
||||||
self.update_incidence_list(e, e_entry.adjacent, f_entry.next, true);
|
self.update_incidence_list(e, e_entry.adjacent, f_entry.next, true);
|
||||||
|
|||||||
Reference in New Issue
Block a user