use crate::backend::native::types::*;
use std::cell::RefCell;
use std::collections::HashMap;
#[inline(always)]
fn unlikely(cond: bool) -> bool {
cond
}
thread_local! {
static NEIGHBOR_POINTER_TABLE: RefCell<NeighborPointerTable> = RefCell::new(NeighborPointerTable::new());
static NODE_HOT_CACHE: RefCell<NodeHotCache> = RefCell::new(NodeHotCache::new());
}
pub struct NeighborPointerTable {
outgoing_edges: HashMap<NativeNodeId, Vec<FileOffset>>,
incoming_edges: HashMap<NativeNodeId, Vec<FileOffset>>,
}
impl NeighborPointerTable {
pub fn new() -> Self {
Self {
outgoing_edges: HashMap::new(),
incoming_edges: HashMap::new(),
}
}
#[inline(always)]
pub fn register_edge(
&mut self,
from_id: NativeNodeId,
to_id: NativeNodeId,
edge_offset: FileOffset,
) {
self.outgoing_edges
.entry(from_id)
.or_insert_with(Vec::new)
.push(edge_offset);
self.incoming_edges
.entry(to_id)
.or_insert_with(Vec::new)
.push(edge_offset);
}
#[inline(always)]
pub fn get_outgoing_edges(&self, node_id: NativeNodeId) -> Option<&[FileOffset]> {
self.outgoing_edges.get(&node_id).map(|v| v.as_slice())
}
#[inline(always)]
pub fn get_incoming_edges(&self, node_id: NativeNodeId) -> Option<&[FileOffset]> {
self.incoming_edges.get(&node_id).map(|v| v.as_slice())
}
pub fn clear(&mut self) {
self.outgoing_edges.clear();
self.incoming_edges.clear();
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct NodeHot {
pub outgoing_edge_count: u32,
pub outgoing_cluster_offset: u64,
pub incoming_edge_count: u32,
pub incoming_cluster_offset: u64,
}
pub struct NodeHotCache {
cache: HashMap<NativeNodeId, NodeHot>,
max_size: usize,
}
impl NodeHotCache {
pub fn new() -> Self {
Self::with_capacity(1000)
}
pub fn with_capacity(max_size: usize) -> Self {
Self {
cache: HashMap::new(),
max_size,
}
}
#[inline(always)]
pub fn get(&self, node_id: NativeNodeId) -> Option<&NodeHot> {
self.cache.get(&node_id)
}
#[inline(always)]
pub fn put(&mut self, node_id: NativeNodeId, metadata: NodeHot) {
if self.cache.len() >= self.max_size {
self.cache.clear();
}
self.cache.insert(node_id, metadata);
}
pub fn clear(&mut self) {
self.cache.clear();
}
}
#[inline(always)]
pub fn with_neighbor_pointer_table<F, R>(f: F) -> R
where
F: FnOnce(&mut NeighborPointerTable) -> R,
{
NEIGHBOR_POINTER_TABLE.with(|table| f(&mut table.borrow_mut()))
}
#[inline(always)]
pub fn with_node_hot_cache<F, R>(f: F) -> R
where
F: FnOnce(&mut NodeHotCache) -> R,
{
NODE_HOT_CACHE.with(|cache| f(&mut cache.borrow_mut()))
}
pub fn register_edge_offset(from_id: NativeNodeId, to_id: NativeNodeId, edge_offset: FileOffset) {
if from_id <= 0 || to_id <= 0 || edge_offset == 0 {
return;
}
with_neighbor_pointer_table(|table| {
let should_check_capacity = unlikely(table.outgoing_edges.len() >= 10000);
table.register_edge(from_id, to_id, edge_offset);
if should_check_capacity {
}
});
}
#[inline(always)]
pub fn get_outgoing_edge_offsets(node_id: NativeNodeId) -> Option<Vec<FileOffset>> {
with_neighbor_pointer_table(|table| {
table
.get_outgoing_edges(node_id)
.map(|offsets| offsets.to_vec())
})
}
#[inline(always)]
pub fn get_incoming_edge_offsets(node_id: NativeNodeId) -> Option<Vec<FileOffset>> {
with_neighbor_pointer_table(|table| {
table
.get_incoming_edges(node_id)
.map(|offsets| offsets.to_vec())
})
}
#[inline(always)]
pub fn get_node_hot(node_id: NativeNodeId) -> Option<NodeHot> {
with_node_hot_cache(|cache| cache.get(node_id).cloned())
}
#[inline(always)]
pub fn put_node_hot(node_id: NativeNodeId, metadata: NodeHot) {
with_node_hot_cache(|cache| {
cache.put(node_id, metadata);
});
}
#[inline(always)]
pub fn extract_node_hot(node: &crate::backend::native::types::NodeRecord) -> NodeHot {
NodeHot {
outgoing_edge_count: node.outgoing_edge_count,
outgoing_cluster_offset: node.outgoing_cluster_offset,
incoming_edge_count: node.incoming_edge_count,
incoming_cluster_offset: node.incoming_cluster_offset,
}
}
pub fn clear_all_caches() {
with_neighbor_pointer_table(|table| table.clear());
with_node_hot_cache(|cache| cache.clear());
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_neighbor_pointer_table_basic() {
let mut table = NeighborPointerTable::new();
table.register_edge(1, 2, 1000);
table.register_edge(1, 3, 1256);
table.register_edge(2, 1, 1512);
let outgoing = table.get_outgoing_edges(1).unwrap();
assert_eq!(outgoing, &[1000, 1256]);
let outgoing = table.get_outgoing_edges(2).unwrap();
assert_eq!(outgoing, &[1512]);
let incoming = table.get_incoming_edges(2).unwrap();
assert_eq!(incoming, &[1000]);
let incoming = table.get_incoming_edges(1).unwrap();
assert_eq!(incoming, &[1512]);
assert!(table.get_outgoing_edges(999).is_none());
assert!(table.get_incoming_edges(999).is_none());
}
#[test]
fn test_node_hot_cache_basic() {
let mut cache = NodeHotCache::with_capacity(2);
let metadata1 = NodeHot {
outgoing_edge_count: 5,
outgoing_cluster_offset: 1000,
incoming_edge_count: 3,
incoming_cluster_offset: 2000,
};
let metadata2 = NodeHot {
outgoing_edge_count: 2,
outgoing_cluster_offset: 3000,
incoming_edge_count: 7,
incoming_cluster_offset: 4000,
};
cache.put(1, metadata1.clone());
cache.put(2, metadata2.clone());
assert_eq!(cache.get(1), Some(&metadata1));
assert_eq!(cache.get(2), Some(&metadata2));
assert!(cache.get(3).is_none());
let metadata3 = NodeHot {
outgoing_edge_count: 1,
outgoing_cluster_offset: 5000,
incoming_edge_count: 1,
incoming_cluster_offset: 6000,
};
cache.put(3, metadata3);
assert!(cache.get(1).is_none());
assert!(cache.get(2).is_none());
assert_eq!(
cache.get(3),
Some(&NodeHot {
outgoing_edge_count: 1,
outgoing_cluster_offset: 5000,
incoming_edge_count: 1,
incoming_cluster_offset: 6000,
})
);
}
}