Repository: qdrant/qdrant
File: lib/segment/src/index/hnsw_index/graph_layers.rs (Rust)

Code before:
        is_stopped: &AtomicBool,
    ) -> CancellableResult<ScoredPointOffset> {
        let mut links_buffer = Vec::new();
        let mut current_point = ScoredPointOffset {
            idx: entry_point,
            score: points_scorer.score_point(entry_point),
        };
        for level in rev_range(top_level, target_level) {
            check_process_stopped(is_stopped)?;
            current_point =
                self.search_entry_on_level(current_point, level, points_scorer, &mut links_buffer);
        }
        Ok(current_point)
    }

    fn search_entry_on_level(
        &self,
        entry_point: ScoredPointOffset,
        level: usize,
        points_scorer: &mut FilteredScorer,
        links: &mut Vec<PointOffsetType>,
    ) -> ScoredPointOffset {
        let limit = self.get_m(level);

        links.clear();
        links.reserve(limit);

        let mut changed = true;
        let mut current_point = entry_point;
        while changed {
            changed = false;

            links.clear();
            self.links_map(current_point.idx, level, |link| {
                links.push(link);
            });

            points_scorer
                .score_points(links, limit)
                .for_each(|score_point| {
                    if score_point.score > current_point.score {
                        changed = true;
                        current_point = score_point;
                    }
                });
        }
        current_point
    }
}

impl GraphLayersBase for GraphLayers {

Review comment (performance):
Why do we change the preallocated size? I see that filling of `links` is done by `links_map`, which may give more than `M` values in case of filtered search - we apply filter later. I don't remember why we use such estimation, but I propose to keep it as is

Code after:
        top_level: usize,
        target_level: usize,
        points_scorer: &mut FilteredScorer,
        is_stopped: &AtomicBool,
    ) -> CancellableResult<ScoredPointOffset> {
        let mut links_buffer = Vec::new();
        let mut result = None;
        let mut level_entry = entry_point;
        for level in rev_range(top_level, target_level) {
            check_process_stopped(is_stopped)?;
            let search_result =
                self.search_entry_on_level(level_entry, level, points_scorer, &mut links_buffer);
            level_entry = search_result.idx;
            result = Some(search_result);
        }
        if let Some(result) = result {
            Ok(result)
        } else {
            // If no levels, return the entry point with it's score
            Ok(ScoredPointOffset {
                idx: entry_point,
                score: points_scorer.score_point(entry_point),
            })
        }
    }

    fn search_entry_on_level(
        &self,
        entry_point: PointOffsetType,
        level: usize,
        points_scorer: &mut FilteredScorer,
        // Temporary buffer for links to avoid unnecessary allocations.
        // 'links' is reused if `search_entry_on_level` is called multiple times.
        links: &mut Vec<PointOffsetType>,
    ) -> ScoredPointOffset {
        let limit = self.get_m(level);

        links.clear();
        links.reserve(2 * self.get_m(0));

        let mut changed = true;
        let mut current_point = ScoredPointOffset {
            idx: entry_point,
            score: points_scorer.score_point(entry_point),
        };
        while changed {
            changed = false;

            links.clear();
            self.for_each_link(current_point.idx, level, |link| {
                links.push(link);