use super::super::distance::{batch_distance_with_prefetch, DistanceEngine};
use super::super::layer::{Layer, NodeId};
use super::super::ordered_float::OrderedFloat;
use super::search_state::{gather_unvisited_neighbors, process_batch_results, SearchState};
use crate::perf_optimizations::ContiguousVectors;
use smallvec::SmallVec;
use std::cmp::Reverse;
#[inline]
#[allow(clippy::too_many_arguments)]
pub(in crate::index::hnsw::native::graph) fn search_layer_pipelined<D: DistanceEngine>(
distance: &D,
query: &[f32],
vectors: &ContiguousVectors,
layers: &[Layer],
state: &mut SearchState,
ef: usize,
layer: usize,
stagnation_limit: usize,
use_prefetch: bool,
) {
while let Some(Reverse((OrderedFloat(c_dist), c_node))) = state.candidates.pop() {
if state.should_terminate(c_dist, ef, stagnation_limit) {
break;
}
let improved = gather_prefetch_compute(
distance,
query,
vectors,
layers,
state,
ef,
layer,
c_node,
use_prefetch,
);
state.update_stagnation(improved);
}
}
#[inline]
#[allow(clippy::too_many_arguments)]
fn gather_prefetch_compute<D: DistanceEngine>(
distance: &D,
query: &[f32],
vectors: &ContiguousVectors,
layers: &[Layer],
state: &mut SearchState,
ef: usize,
layer: usize,
c_node: NodeId,
use_prefetch: bool,
) -> bool {
let batch = layers[layer]
.with_neighbors(c_node, |neighbors| {
gather_unvisited_neighbors(neighbors, &mut state.visited, vectors, use_prefetch)
})
.unwrap_or_default();
if batch.is_empty() {
return false;
}
if use_prefetch {
prefetch_next_candidate(state, layers, layer, vectors);
}
compute_and_process(distance, query, &batch, ef, state)
}
#[inline]
fn prefetch_next_candidate(
state: &SearchState,
layers: &[Layer],
layer: usize,
vectors: &ContiguousVectors,
) {
let Some(Reverse((_, peek_node))) = state.candidates.peek() else {
return;
};
let peek_node = *peek_node;
layers[layer].with_neighbors(peek_node, |neighbors| {
prefetch_neighbor_vectors(neighbors, vectors);
});
}
#[inline]
fn prefetch_neighbor_vectors(neighbors: &[NodeId], vectors: &ContiguousVectors) {
for &neighbor in neighbors {
vectors.prefetch(neighbor);
}
}
#[inline]
fn compute_and_process<D: DistanceEngine>(
distance: &D,
query: &[f32],
batch: &[(NodeId, &[f32])],
ef: usize,
state: &mut SearchState,
) -> bool {
let vecs: SmallVec<[&[f32]; 32]> = batch.iter().map(|(_, v)| *v).collect();
let distances = batch_distance_with_prefetch(distance, query, &vecs);
process_batch_results(batch, &distances, ef, state)
}