77#include " deglib/search/result_list.h"
88
99#include < algorithm>
10+ #include < chrono>
1011#include < array>
1112#include < cstddef>
1213#include < cstdint>
@@ -119,9 +120,11 @@ class InternalGraph {
119120
120121 protected:
121122 std::vector<uint32_t > entry_vertex_indices_{0 };
122- int32_t po_ = 8 ;
123- int32_t pl_ = 3 ;
124- int32_t nl_ = 3 ;
123+ // Prefetch tuning knobs: performance cache state, not logical identity, hence mutable so
124+ // optimize()/setPrefetch() can run through a const InternalGraph reference.
125+ mutable int32_t po_ = 8 ;
126+ mutable int32_t pl_ = 3 ;
127+ mutable int32_t nl_ = 3 ;
125128
126129 public:
127130 const std::vector<uint32_t >& getEntryVertexIndices () const { return entry_vertex_indices_; }
@@ -133,20 +136,83 @@ class InternalGraph {
133136 int32_t getPo () const noexcept { return po_; }
134137 int32_t getPl () const noexcept { return pl_; }
135138 int32_t getNl () const noexcept { return nl_; }
136- void setPo (int32_t po) noexcept {
139+ void setPo (int32_t po) const noexcept {
137140 if (po > 0 ) po_ = po;
138141 }
139- void setPl (int32_t pl) noexcept {
142+ void setPl (int32_t pl) const noexcept {
140143 if (pl > 0 ) pl_ = pl;
141144 }
142- void setNl (int32_t nl) noexcept {
145+ void setNl (int32_t nl) const noexcept {
143146 if (nl > 0 ) nl_ = nl;
144147 }
145- void setPrefetch (int32_t po, int32_t pl, int32_t nl = 3 ) noexcept {
148+ void setPrefetch (int32_t po, int32_t pl, int32_t nl = 3 ) const noexcept {
146149 if (po > 0 ) po_ = po;
147150 if (pl > 0 ) pl_ = pl;
148151 if (nl > 0 ) nl_ = nl;
149152 }
153+
154+ /* *
155+ * Auto-tunes the traversal prefetch parameters (po, pl, nl) by empirically timing graph
156+ * traversal over sampled vertices. Mirrors Reranker::optimize: the graph self-samples its own
157+ * stored features as queries, so no external query buffer or quantization round-trip is needed
158+ * and the timing exercises the real traversal path (search_ef_intern).
159+ *
160+ * @param sample_count Number of vertices sampled as queries (capped at the graph size).
161+ * @param k Result count per traversal (the expected query operating point).
162+ * @param ef Beam width per traversal; clamped to at least k.
163+ * @param seed Random seed for query sampling.
164+ */
165+ void optimize (size_t sample_count = 50 , uint32_t k = 100 , uint32_t ef = 200 , uint32_t seed = 7 ) const {
166+ const uint32_t n_vertices = size ();
167+ const size_t n_queries = std::min (sample_count, static_cast <size_t >(n_vertices));
168+ // Below one full neighborhood the traversal does no real work; the timing would be meaningless.
169+ if (n_queries == 0 || k == 0 || n_queries < getEdgesPerVertex ()) return ;
170+ ef = std::max (ef, k);
171+
172+ std::vector<uint32_t > sample_idx (n_queries);
173+ uint32_t rng = seed * 2654435761u + 1u ;
174+ for (auto & idx : sample_idx) {
175+ rng ^= rng << 13 ;
176+ rng ^= rng >> 17 ;
177+ rng ^= rng << 5 ;
178+ idx = rng % n_vertices;
179+ }
180+
181+ const std::vector<int32_t > try_pos = {4 , 8 , 12 , 16 };
182+ const std::vector<int32_t > try_pls = {2 , 3 , 4 };
183+ const std::vector<int32_t > try_nls = {2 , 3 , 4 };
184+
185+ const std::vector<uint32_t >& entries = getEntryVertexIndices ();
186+ int32_t best_po = po_;
187+ int32_t best_pl = pl_;
188+ int32_t best_nl = nl_;
189+ double best_time = std::numeric_limits<double >::max ();
190+
191+ for (int32_t po : try_pos) {
192+ for (int32_t pl : try_pls) {
193+ for (int32_t nl : try_nls) {
194+ setPrefetch (po, pl, nl);
195+
196+ for (size_t i = 0 ; i < std::min<size_t >(3 , n_queries); ++i) {
197+ search_ef_intern (entries, getFeatureVector (sample_idx[i]), k, ef);
198+ }
199+ const auto t_start = std::chrono::high_resolution_clock::now ();
200+ for (size_t i = 0 ; i < n_queries; ++i) {
201+ search_ef_intern (entries, getFeatureVector (sample_idx[i]), k, ef);
202+ }
203+ const double dur = std::chrono::duration<double , std::micro>(std::chrono::high_resolution_clock::now () - t_start).count ();
204+
205+ if (dur < best_time) {
206+ best_time = dur;
207+ best_po = po;
208+ best_pl = pl;
209+ best_nl = nl;
210+ }
211+ }
212+ }
213+ }
214+ setPrefetch (best_po, best_pl, best_nl);
215+ }
150216 /* *
151217 * Perform a search but stops when the to_vertex was found.
152218 */
0 commit comments