use super::super::distance::DistanceEngine;
use super::super::layer::NodeId;
use super::{NativeHnsw, NO_ENTRY_POINT};
use std::collections::VecDeque;
use std::sync::atomic::Ordering;
const REORDER_THRESHOLD: usize = 1000;
impl<D: DistanceEngine> NativeHnsw<D> {
pub fn reorder_for_locality(&self) -> crate::error::Result<()> {
let count = self.count.load(Ordering::Relaxed);
if count < REORDER_THRESHOLD {
return Ok(());
}
let entry = self.entry_point.load(Ordering::Acquire);
if entry == NO_ENTRY_POINT {
return Ok(());
}
let permutation = self.compute_bfs_order(entry, count);
if permutation.is_empty() {
return Ok(());
}
self.apply_permutation(&permutation)?;
#[cfg(feature = "gpu")]
self.invalidate_gpu_caches();
Ok(())
}
fn compute_bfs_order(&self, entry: NodeId, count: usize) -> Vec<NodeId> {
let layers = self.layers.read();
if layers.is_empty() {
return Vec::new();
}
let mut order = Vec::with_capacity(count);
let mut visited = vec![false; count];
let mut queue = VecDeque::with_capacity(count);
if entry < count {
visited[entry] = true;
queue.push_back(entry);
}
self.bfs_walk(&layers[0], &mut queue, &mut visited, &mut order, count);
self.append_unvisited(&visited, &mut order);
order
}
#[allow(clippy::unused_self)] fn bfs_walk(
&self,
layer: &super::super::layer::Layer,
queue: &mut VecDeque<NodeId>,
visited: &mut [bool],
order: &mut Vec<NodeId>,
count: usize,
) {
while let Some(node) = queue.pop_front() {
order.push(node);
let _ = layer.with_neighbors(node, |neighbors| {
for &neighbor in neighbors {
if neighbor < count && !visited[neighbor] {
visited[neighbor] = true;
queue.push_back(neighbor);
}
}
});
}
}
#[allow(clippy::unused_self)] fn append_unvisited(&self, visited: &[bool], order: &mut Vec<NodeId>) {
for (node, &was_visited) in visited.iter().enumerate() {
if !was_visited {
order.push(node);
}
}
}
fn apply_permutation(&self, new_order: &[NodeId]) -> crate::error::Result<()> {
let count = new_order.len();
let old_to_new = Self::build_reverse_mapping(new_order, count);
self.reorder_vectors(new_order)?;
self.remap_neighbor_ids(&old_to_new);
self.update_entry_point(&old_to_new, count);
self.build_columnar_layout();
Ok(())
}
fn build_columnar_layout(&self) {
let vectors_guard = self.vectors.read();
if let Some(vectors) = vectors_guard.as_ref() {
let pdx = super::super::columnar_vectors::ColumnarVectors::from_contiguous(vectors);
*self.columnar.write() = Some(pdx);
}
}
fn build_reverse_mapping(new_order: &[NodeId], count: usize) -> Vec<usize> {
let mut old_to_new = vec![0usize; count];
for (new_id, &old_id) in new_order.iter().enumerate() {
if old_id < count {
old_to_new[old_id] = new_id;
}
}
old_to_new
}
fn reorder_vectors(&self, new_order: &[NodeId]) -> crate::error::Result<()> {
let mut guard = self.vectors.write();
if let Some(storage) = guard.as_mut() {
storage.reorder(new_order)?;
}
Ok(())
}
fn remap_neighbor_ids(&self, old_to_new: &[usize]) {
let mut layers = self.layers.write();
for layer in layers.iter_mut() {
layer.remap_ids(old_to_new);
}
}
fn update_entry_point(&self, old_to_new: &[usize], count: usize) {
let old_ep = self.entry_point.load(Ordering::Acquire);
if old_ep != NO_ENTRY_POINT && old_ep < count {
self.entry_point
.store(old_to_new[old_ep], Ordering::Release);
}
}
}
#[cfg(test)]
#[path = "reorder_tests.rs"]
mod tests;