Krotos Modules 3
Loading...
Searching...
No Matches
hnswalg.h
Go to the documentation of this file.
1#pragma once
2
3#include "visited_list_pool.h"
4#include "hnswlib.h"
5#include <atomic>
6#include <random>
7#include <stdlib.h>
8#include <assert.h>
9#include <unordered_set>
10#include <list>
11
12namespace hnswlib {
13 typedef unsigned int tableint;
14 typedef unsigned int linklistsizeint;
15
16 template<typename dist_t>
17 class HierarchicalNSW : public AlgorithmInterface<dist_t> {
18 public:
19 static const tableint max_update_element_locks = 65536;
22
23 HierarchicalNSW(SpaceInterface<dist_t> *s, const std::string &location, bool nmslib = false, size_t max_elements=0) {
24 loadIndex(location, s, nmslib, max_elements);
25 }
26
27 HierarchicalNSW(SpaceInterface<dist_t> *s, size_t max_elements, size_t M = 16, size_t ef_construction = 200, size_t random_seed = 100) :
29 max_elements_ = max_elements;
30
31 num_deleted_ = 0;
35 M_ = M;
36 maxM_ = M_;
37 maxM0_ = M_ * 2;
38 ef_construction_ = std::max(ef_construction,M_);
39 ef_ = 10;
40
41 level_generator_.seed(random_seed);
42 update_probability_generator_.seed(random_seed + 1);
43
48 offsetLevel0_ = 0;
49
51 if (data_level0_memory_ == nullptr)
52 throw std::runtime_error("Not enough memory");
53
55
56 visited_list_pool_ = new VisitedListPool(1, max_elements);
57
58 //initializations for special treatment of the first node
60 maxlevel_ = -1;
61
62 linkLists_ = (char **) malloc(sizeof(void *) * max_elements_);
63 if (linkLists_ == nullptr)
64 throw std::runtime_error("Not enough memory: HierarchicalNSW failed to allocate linklists");
66 mult_ = 1 / log(1.0 * M_);
67 revSize_ = 1.0 / mult_;
68 }
69
71 constexpr bool operator()(std::pair<dist_t, tableint> const &a,
72 std::pair<dist_t, tableint> const &b) const noexcept {
73 return a.first < b.first;
74 }
75 };
76
78
80 for (tableint i = 0; i < cur_element_count; i++) {
81 if (element_levels_[i] > 0)
82 free(linkLists_[i]);
83 }
84 free(linkLists_);
85 delete visited_list_pool_;
86 }
87
93
94 size_t M_;
95 size_t maxM_;
96 size_t maxM0_;
98
99 double mult_, revSize_;
101
102
105
106 std::vector<std::mutex> link_list_locks_;
107
108 // Locks to prevent race condition during update/insert of an element at same time.
109 // Note: Locks for additions can also be used to prevent this race condition if the querying of KNN is not exposed along with update/inserts i.e multithread insert/update/query in parallel.
110 std::vector<std::mutex> link_list_update_locks_;
112
115
118 std::vector<int> element_levels_;
119
121
125 std::unordered_map<labeltype, tableint> label_lookup_;
126
127 std::default_random_engine level_generator_;
128 std::default_random_engine update_probability_generator_;
129
130 inline labeltype getExternalLabel(tableint internal_id) const {
131 labeltype return_label;
132 memcpy(&return_label,(data_level0_memory_ + internal_id * size_data_per_element_ + label_offset_), sizeof(labeltype));
133 return return_label;
134 }
135
136 inline void setExternalLabel(tableint internal_id, labeltype label) const {
137 memcpy((data_level0_memory_ + internal_id * size_data_per_element_ + label_offset_), &label, sizeof(labeltype));
138 }
139
140 inline labeltype *getExternalLabeLp(tableint internal_id) const {
142 }
143
144 inline char *getDataByInternalId(tableint internal_id) const {
145 return (data_level0_memory_ + internal_id * size_data_per_element_ + offsetData_);
146 }
147
148 int getRandomLevel(double reverse_size) {
149 std::uniform_real_distribution<double> distribution(0.0, 1.0);
150 double r = -log(distribution(level_generator_)) * reverse_size;
151 return (int) r;
152 }
153
154
155 std::priority_queue<std::pair<dist_t, tableint>, std::vector<std::pair<dist_t, tableint>>, CompareByFirst>
156 searchBaseLayer(tableint ep_id, const void *data_point, int layer) {
158 vl_type *visited_array = vl->mass;
159 vl_type visited_array_tag = vl->curV;
160
161 std::priority_queue<std::pair<dist_t, tableint>, std::vector<std::pair<dist_t, tableint>>, CompareByFirst> top_candidates;
162 std::priority_queue<std::pair<dist_t, tableint>, std::vector<std::pair<dist_t, tableint>>, CompareByFirst> candidateSet;
163
164 dist_t lowerBound;
165 if (!isMarkedDeleted(ep_id)) {
166 dist_t dist = fstdistfunc_(data_point, getDataByInternalId(ep_id), dist_func_param_);
167 top_candidates.emplace(dist, ep_id);
168 lowerBound = dist;
169 candidateSet.emplace(-dist, ep_id);
170 } else {
171 lowerBound = std::numeric_limits<dist_t>::max();
172 candidateSet.emplace(-lowerBound, ep_id);
173 }
174 visited_array[ep_id] = visited_array_tag;
175
176 while (!candidateSet.empty()) {
177 std::pair<dist_t, tableint> curr_el_pair = candidateSet.top();
178 if ((-curr_el_pair.first) > lowerBound && top_candidates.size() == ef_construction_) {
179 break;
180 }
181 candidateSet.pop();
182
183 tableint curNodeNum = curr_el_pair.second;
184
185 std::unique_lock <std::mutex> lock(link_list_locks_[curNodeNum]);
186
187 int *data;// = (int *)(linkList0_ + curNodeNum * size_links_per_element0_);
188 if (layer == 0) {
189 data = (int*)get_linklist0(curNodeNum);
190 } else {
191 data = (int*)get_linklist(curNodeNum, layer);
192// data = (int *) (linkLists_[curNodeNum] + (layer - 1) * size_links_per_element_);
193 }
194 size_t size = getListCount((linklistsizeint*)data);
195 tableint *datal = (tableint *) (data + 1);
196#ifdef USE_SSE
197 _mm_prefetch((char *) (visited_array + *(data + 1)), _MM_HINT_T0);
198 _mm_prefetch((char *) (visited_array + *(data + 1) + 64), _MM_HINT_T0);
199 _mm_prefetch(getDataByInternalId(*datal), _MM_HINT_T0);
200 _mm_prefetch(getDataByInternalId(*(datal + 1)), _MM_HINT_T0);
201#endif
202
203 for (size_t j = 0; j < size; j++) {
204 tableint candidate_id = *(datal + j);
205// if (candidate_id == 0) continue;
206#ifdef USE_SSE
207 _mm_prefetch((char *) (visited_array + *(datal + j + 1)), _MM_HINT_T0);
208 _mm_prefetch(getDataByInternalId(*(datal + j + 1)), _MM_HINT_T0);
209#endif
210 if (visited_array[candidate_id] == visited_array_tag) continue;
211 visited_array[candidate_id] = visited_array_tag;
212 char *currObj1 = (getDataByInternalId(candidate_id));
213
214 dist_t dist1 = fstdistfunc_(data_point, currObj1, dist_func_param_);
215 if (top_candidates.size() < ef_construction_ || lowerBound > dist1) {
216 candidateSet.emplace(-dist1, candidate_id);
217#ifdef USE_SSE
218 _mm_prefetch(getDataByInternalId(candidateSet.top().second), _MM_HINT_T0);
219#endif
220
221 if (!isMarkedDeleted(candidate_id))
222 top_candidates.emplace(dist1, candidate_id);
223
224 if (top_candidates.size() > ef_construction_)
225 top_candidates.pop();
226
227 if (!top_candidates.empty())
228 lowerBound = top_candidates.top().first;
229 }
230 }
231 }
233
234 return top_candidates;
235 }
236
237 mutable std::atomic<long> metric_distance_computations;
238 mutable std::atomic<long> metric_hops;
239
240 template <bool has_deletions, bool collect_metrics=false>
241 std::priority_queue<std::pair<dist_t, tableint>, std::vector<std::pair<dist_t, tableint>>, CompareByFirst>
242 searchBaseLayerST(tableint ep_id, const void *data_point, size_t ef) const {
244 vl_type *visited_array = vl->mass;
245 vl_type visited_array_tag = vl->curV;
246
247 std::priority_queue<std::pair<dist_t, tableint>, std::vector<std::pair<dist_t, tableint>>, CompareByFirst> top_candidates;
248 std::priority_queue<std::pair<dist_t, tableint>, std::vector<std::pair<dist_t, tableint>>, CompareByFirst> candidate_set;
249
250 dist_t lowerBound;
251 if (!has_deletions || !isMarkedDeleted(ep_id)) {
252 dist_t dist = fstdistfunc_(data_point, getDataByInternalId(ep_id), dist_func_param_);
253 lowerBound = dist;
254 top_candidates.emplace(dist, ep_id);
255 candidate_set.emplace(-dist, ep_id);
256 } else {
257 lowerBound = std::numeric_limits<dist_t>::max();
258 candidate_set.emplace(-lowerBound, ep_id);
259 }
260
261 visited_array[ep_id] = visited_array_tag;
262
263 while (!candidate_set.empty()) {
264
265 std::pair<dist_t, tableint> current_node_pair = candidate_set.top();
266
267 if ((-current_node_pair.first) > lowerBound && (top_candidates.size() == ef || has_deletions == false)) {
268 break;
269 }
270 candidate_set.pop();
271
272 tableint current_node_id = current_node_pair.second;
273 int *data = (int *) get_linklist0(current_node_id);
274 size_t size = getListCount((linklistsizeint*)data);
275// bool cur_node_deleted = isMarkedDeleted(current_node_id);
276 if(collect_metrics){
277 metric_hops++;
279 }
280
281#ifdef USE_SSE
282 _mm_prefetch((char *) (visited_array + *(data + 1)), _MM_HINT_T0);
283 _mm_prefetch((char *) (visited_array + *(data + 1) + 64), _MM_HINT_T0);
284 _mm_prefetch(data_level0_memory_ + (*(data + 1)) * size_data_per_element_ + offsetData_, _MM_HINT_T0);
285 _mm_prefetch((char *) (data + 2), _MM_HINT_T0);
286#endif
287
288 for (size_t j = 1; j <= size; j++) {
289 int candidate_id = *(data + j);
290// if (candidate_id == 0) continue;
291#ifdef USE_SSE
292 _mm_prefetch((char *) (visited_array + *(data + j + 1)), _MM_HINT_T0);
293 _mm_prefetch(data_level0_memory_ + (*(data + j + 1)) * size_data_per_element_ + offsetData_,
294 _MM_HINT_T0);
295#endif
296 if (!(visited_array[candidate_id] == visited_array_tag)) {
297
298 visited_array[candidate_id] = visited_array_tag;
299
300 char *currObj1 = (getDataByInternalId(candidate_id));
301 dist_t dist = fstdistfunc_(data_point, currObj1, dist_func_param_);
302
303 if (top_candidates.size() < ef || lowerBound > dist) {
304 candidate_set.emplace(-dist, candidate_id);
305#ifdef USE_SSE
306 _mm_prefetch(data_level0_memory_ + candidate_set.top().second * size_data_per_element_ +
308 _MM_HINT_T0);
309#endif
310
311 if (!has_deletions || !isMarkedDeleted(candidate_id))
312 top_candidates.emplace(dist, candidate_id);
313
314 if (top_candidates.size() > ef)
315 top_candidates.pop();
316
317 if (!top_candidates.empty())
318 lowerBound = top_candidates.top().first;
319 }
320 }
321 }
322 }
323
325 return top_candidates;
326 }
327
329 std::priority_queue<std::pair<dist_t, tableint>, std::vector<std::pair<dist_t, tableint>>, CompareByFirst> &top_candidates,
330 const size_t M) {
331 if (top_candidates.size() < M) {
332 return;
333 }
334
335 std::priority_queue<std::pair<dist_t, tableint>> queue_closest;
336 std::vector<std::pair<dist_t, tableint>> return_list;
337 while (top_candidates.size() > 0) {
338 queue_closest.emplace(-top_candidates.top().first, top_candidates.top().second);
339 top_candidates.pop();
340 }
341
342 while (queue_closest.size()) {
343 if (return_list.size() >= M)
344 break;
345 std::pair<dist_t, tableint> curent_pair = queue_closest.top();
346 dist_t dist_to_query = -curent_pair.first;
347 queue_closest.pop();
348 bool good = true;
349
350 for (std::pair<dist_t, tableint> second_pair : return_list) {
351 dist_t curdist =
352 fstdistfunc_(getDataByInternalId(second_pair.second),
353 getDataByInternalId(curent_pair.second),
355 if (curdist < dist_to_query) {
356 good = false;
357 break;
358 }
359 }
360 if (good) {
361 return_list.push_back(curent_pair);
362 }
363 }
364
365 for (std::pair<dist_t, tableint> curent_pair : return_list) {
366 top_candidates.emplace(-curent_pair.first, curent_pair.second);
367 }
368 }
369
370
373 };
374
375// linklistsizeint *get_linklist0(tableint internal_id, char *data_level0_memory_) const {
376// return (linklistsizeint *) (data_level0_memory_ + internal_id * size_data_per_element_ + offsetLevel0_);
377// };
378
379 linklistsizeint *get_linklist(tableint internal_id, int level) const {
380 return (linklistsizeint *) (linkLists_[internal_id] + (level - 1) * size_links_per_element_);
381 };
382
383 linklistsizeint *get_linklist_at_level(tableint internal_id, int level) const {
384 return level == 0 ? get_linklist0(internal_id) : get_linklist(internal_id, level);
385 };
386
387 tableint mutuallyConnectNewElement(const void */*data_point*/, tableint cur_c,
388 std::priority_queue<std::pair<dist_t, tableint>, std::vector<std::pair<dist_t, tableint>>, CompareByFirst> &top_candidates,
389 int level, bool isUpdate) {
390 size_t Mcurmax = level ? maxM_ : maxM0_;
391 getNeighborsByHeuristic2(top_candidates, M_);
392 if (top_candidates.size() > M_)
393 throw std::runtime_error("Should be not be more than M_ candidates returned by the heuristic");
394
395 std::vector<tableint> selectedNeighbors;
396 selectedNeighbors.reserve(M_);
397 while (top_candidates.size() > 0) {
398 selectedNeighbors.push_back(top_candidates.top().second);
399 top_candidates.pop();
400 }
401
402 tableint next_closest_entry_point = selectedNeighbors.back();
403
404 {
405 linklistsizeint *ll_cur;
406 if (level == 0)
407 ll_cur = get_linklist0(cur_c);
408 else
409 ll_cur = get_linklist(cur_c, level);
410
411 if (*ll_cur && !isUpdate) {
412 throw std::runtime_error("The newly inserted element should have blank link list");
413 }
414 setListCount(ll_cur,selectedNeighbors.size());
415 tableint *data = (tableint *) (ll_cur + 1);
416 for (size_t idx = 0; idx < selectedNeighbors.size(); idx++) {
417 if (data[idx] && !isUpdate)
418 throw std::runtime_error("Possible memory corruption");
419 if (level > element_levels_[selectedNeighbors[idx]])
420 throw std::runtime_error("Trying to make a link on a non-existent level");
421
422 data[idx] = selectedNeighbors[idx];
423
424 }
425 }
426
427 for (size_t idx = 0; idx < selectedNeighbors.size(); idx++) {
428
429 std::unique_lock <std::mutex> lock(link_list_locks_[selectedNeighbors[idx]]);
430
431 linklistsizeint *ll_other;
432 if (level == 0)
433 ll_other = get_linklist0(selectedNeighbors[idx]);
434 else
435 ll_other = get_linklist(selectedNeighbors[idx], level);
436
437 size_t sz_link_list_other = getListCount(ll_other);
438
439 if (sz_link_list_other > Mcurmax)
440 throw std::runtime_error("Bad value of sz_link_list_other");
441 if (selectedNeighbors[idx] == cur_c)
442 throw std::runtime_error("Trying to connect an element to itself");
443 if (level > element_levels_[selectedNeighbors[idx]])
444 throw std::runtime_error("Trying to make a link on a non-existent level");
445
446 tableint *data = (tableint *) (ll_other + 1);
447
448 bool is_cur_c_present = false;
449 if (isUpdate) {
450 for (size_t j = 0; j < sz_link_list_other; j++) {
451 if (data[j] == cur_c) {
452 is_cur_c_present = true;
453 break;
454 }
455 }
456 }
457
458 // If cur_c is already present in the neighboring connections of `selectedNeighbors[idx]` then no need to modify any connections or run the heuristics.
459 if (!is_cur_c_present) {
460 if (sz_link_list_other < Mcurmax) {
461 data[sz_link_list_other] = cur_c;
462 setListCount(ll_other, sz_link_list_other + 1);
463 } else {
464 // finding the "weakest" element to replace it with the new one
465 dist_t d_max = fstdistfunc_(getDataByInternalId(cur_c), getDataByInternalId(selectedNeighbors[idx]),
467 // Heuristic:
468 std::priority_queue<std::pair<dist_t, tableint>, std::vector<std::pair<dist_t, tableint>>, CompareByFirst> candidates;
469 candidates.emplace(d_max, cur_c);
470
471 for (size_t j = 0; j < sz_link_list_other; j++) {
472 candidates.emplace(
473 fstdistfunc_(getDataByInternalId(data[j]), getDataByInternalId(selectedNeighbors[idx]),
474 dist_func_param_), data[j]);
475 }
476
477 getNeighborsByHeuristic2(candidates, Mcurmax);
478
479 int indx = 0;
480 while (candidates.size() > 0) {
481 data[indx] = candidates.top().second;
482 candidates.pop();
483 indx++;
484 }
485
486 setListCount(ll_other, indx);
487 // Nearest K:
488 /*int indx = -1;
489 for (int j = 0; j < sz_link_list_other; j++) {
490 dist_t d = fstdistfunc_(getDataByInternalId(data[j]), getDataByInternalId(rez[idx]), dist_func_param_);
491 if (d > d_max) {
492 indx = j;
493 d_max = d;
494 }
495 }
496 if (indx >= 0) {
497 data[indx] = cur_c;
498 } */
499 }
500 }
501 }
502
503 return next_closest_entry_point;
504 }
505
506 std::mutex global;
507 size_t ef_;
508
509 void setEf(size_t ef) {
510 ef_ = ef;
511 }
512
513
514 std::priority_queue<std::pair<dist_t, tableint>> searchKnnInternal(void *query_data, int k) {
515 std::priority_queue<std::pair<dist_t, tableint >> top_candidates;
516 if (cur_element_count == 0) return top_candidates;
517 tableint currObj = enterpoint_node_;
519
520 for (size_t level = maxlevel_; level > 0; level--) {
521 bool changed = true;
522 while (changed) {
523 changed = false;
524 int *data;
525 data = (int *) get_linklist(currObj,level);
526 int size = getListCount(data);
527 tableint *datal = (tableint *) (data + 1);
528 for (int i = 0; i < size; i++) {
529 tableint cand = datal[i];
530 if (cand < 0 || cand > max_elements_)
531 throw std::runtime_error("cand error");
532 dist_t d = fstdistfunc_(query_data, getDataByInternalId(cand), dist_func_param_);
533
534 if (d < curdist) {
535 curdist = d;
536 currObj = cand;
537 changed = true;
538 }
539 }
540 }
541 }
542
543 if (num_deleted_) {
544 std::priority_queue<std::pair<dist_t, tableint >> top_candidates1=searchBaseLayerST<true>(currObj, query_data,
545 ef_);
546 top_candidates.swap(top_candidates1);
547 }
548 else{
549 std::priority_queue<std::pair<dist_t, tableint >> top_candidates1=searchBaseLayerST<false>(currObj, query_data,
550 ef_);
551 top_candidates.swap(top_candidates1);
552 }
553
554 while (top_candidates.size() > k) {
555 top_candidates.pop();
556 }
557 return top_candidates;
558 };
559
560 void resizeIndex(size_t new_max_elements){
561 if (new_max_elements<cur_element_count)
562 throw std::runtime_error("Cannot resize, max element is less than the current number of elements");
563
564
565 delete visited_list_pool_;
566 visited_list_pool_ = new VisitedListPool(1, new_max_elements);
567
568
569 element_levels_.resize(new_max_elements);
570
571 std::vector<std::mutex>(new_max_elements).swap(link_list_locks_);
572
573 // Reallocate base layer
574 char * data_level0_memory_new = (char *) realloc(data_level0_memory_, new_max_elements * size_data_per_element_);
575 if (data_level0_memory_new == nullptr)
576 throw std::runtime_error("Not enough memory: resizeIndex failed to allocate base layer");
577 data_level0_memory_ = data_level0_memory_new;
578
579 // Reallocate all other layers
580 char ** linkLists_new = (char **) realloc(linkLists_, sizeof(void *) * new_max_elements);
581 if (linkLists_new == nullptr)
582 throw std::runtime_error("Not enough memory: resizeIndex failed to allocate other layers");
583 linkLists_ = linkLists_new;
584
585 max_elements_ = new_max_elements;
586 }
587
588 void saveIndex(const std::string &location) {
589 std::ofstream output(location, std::ios::binary);
590 std::streampos position;
591
598 writeBinaryPOD(output, maxlevel_);
600 writeBinaryPOD(output, maxM_);
601
602 writeBinaryPOD(output, maxM0_);
603 writeBinaryPOD(output, M_);
604 writeBinaryPOD(output, mult_);
606
608
609 for (size_t i = 0; i < cur_element_count; i++) {
610 unsigned int linkListSize = element_levels_[i] > 0 ? size_links_per_element_ * element_levels_[i] : 0;
611 writeBinaryPOD(output, linkListSize);
612 if (linkListSize)
613 output.write(linkLists_[i], linkListSize);
614 }
615 output.close();
616 }
617
618 void loadIndex(const std::string &location, SpaceInterface<dist_t> *s, bool, size_t max_elements_i=0) {
619 std::ifstream input(location, std::ios::binary);
620
621 if (!input.is_open())
622 throw std::runtime_error("Cannot open file");
623
624 // get file size:
625 input.seekg(0,input.end);
626 std::streampos total_filesize=input.tellg();
627 input.seekg(0,input.beg);
628
632
633 size_t max_elements = max_elements_i;
634 if(max_elements < cur_element_count)
635 max_elements = max_elements_;
636 max_elements_ = max_elements;
640 readBinaryPOD(input, maxlevel_);
642
643 readBinaryPOD(input, maxM_);
644 readBinaryPOD(input, maxM0_);
645 readBinaryPOD(input, M_);
646 readBinaryPOD(input, mult_);
648
649
653
654 auto pos=input.tellg();
655
656
658
659 input.seekg(cur_element_count * size_data_per_element_,input.cur);
660 for (size_t i = 0; i < cur_element_count; i++) {
661 if(input.tellg() < 0 || input.tellg()>=total_filesize){
662 throw std::runtime_error("Index seems to be corrupted or unsupported");
663 }
664
665 unsigned int linkListSize;
666 readBinaryPOD(input, linkListSize);
667 if (linkListSize != 0) {
668 input.seekg(linkListSize,input.cur);
669 }
670 }
671
672 // throw exception if it either corrupted or old index
673 if(input.tellg()!=total_filesize)
674 throw std::runtime_error("Index seems to be corrupted or unsupported");
675
676 input.clear();
677
679
680 input.seekg(pos,input.beg);
681
682 data_level0_memory_ = (char *) malloc(max_elements * size_data_per_element_);
683 if (data_level0_memory_ == nullptr)
684 throw std::runtime_error("Not enough memory: loadIndex failed to allocate level0");
686
688
690 std::vector<std::mutex>(max_elements).swap(link_list_locks_);
691 std::vector<std::mutex>(max_update_element_locks).swap(link_list_update_locks_);
692
693 visited_list_pool_ = new VisitedListPool(1, max_elements);
694
695 linkLists_ = (char **) malloc(sizeof(void *) * max_elements);
696 if (linkLists_ == nullptr)
697 throw std::runtime_error("Not enough memory: loadIndex failed to allocate linklists");
698 element_levels_ = std::vector<int>(max_elements);
699 revSize_ = 1.0 / mult_;
700 ef_ = 10;
701 for (size_t i = 0; i < cur_element_count; i++) {
703 unsigned int linkListSize;
704 readBinaryPOD(input, linkListSize);
705 if (linkListSize == 0) {
706 element_levels_[i] = 0;
707
708 linkLists_[i] = nullptr;
709 } else {
710 element_levels_[i] = linkListSize / size_links_per_element_;
711 linkLists_[i] = (char *) malloc(linkListSize);
712 if (linkLists_[i] == nullptr)
713 throw std::runtime_error("Not enough memory: loadIndex failed to allocate linklist");
714 input.read(linkLists_[i], linkListSize);
715 }
716 }
717
718 for (size_t i = 0; i < cur_element_count; i++) {
719 if(isMarkedDeleted(i))
720 num_deleted_ += 1;
721 }
722
723 input.close();
724
725 return;
726 }
727
728 template<typename data_t>
729 std::vector<data_t> getDataByLabel(labeltype label) const
730 {
731 tableint label_c;
732 auto search = label_lookup_.find(label);
733 if (search == label_lookup_.end() || isMarkedDeleted(search->second)) {
734 throw std::runtime_error("Label not found");
735 }
736 label_c = search->second;
737
738 char* data_ptrv = getDataByInternalId(label_c);
739 size_t dim = *((size_t *) dist_func_param_);
740 std::vector<data_t> data;
741 data_t* data_ptr = (data_t*) data_ptrv;
742 for (int i = 0; i < dim; i++) {
743 data.push_back(*data_ptr);
744 data_ptr += 1;
745 }
746 return data;
747 }
748
749 static const unsigned char DELETE_MARK = 0x01;
750 // static const unsigned char REUSE_MARK = 0x10;
756 {
757 auto search = label_lookup_.find(label);
758 if (search == label_lookup_.end()) {
759 throw std::runtime_error("Label not found");
760 }
761 tableint internalId = search->second;
762 markDeletedInternal(internalId);
763 }
764
770 void markDeletedInternal(tableint internalId) {
771 assert(internalId < cur_element_count);
772 if (!isMarkedDeleted(internalId))
773 {
774 unsigned char *ll_cur = ((unsigned char *)get_linklist0(internalId))+2;
775 *ll_cur |= DELETE_MARK;
776 num_deleted_ += 1;
777 }
778 else
779 {
780 throw std::runtime_error("The requested to delete element is already deleted");
781 }
782 }
783
789 {
790 auto search = label_lookup_.find(label);
791 if (search == label_lookup_.end()) {
792 throw std::runtime_error("Label not found");
793 }
794 tableint internalId = search->second;
795 unmarkDeletedInternal(internalId);
796 }
797
803 assert(internalId < cur_element_count);
804 if (isMarkedDeleted(internalId))
805 {
806 unsigned char *ll_cur = ((unsigned char *)get_linklist0(internalId))+2;
807 *ll_cur &= ~DELETE_MARK;
808 num_deleted_ -= 1;
809 }
810 else
811 {
812 throw std::runtime_error("The requested to undelete element is not deleted");
813 }
814 }
815
821 bool isMarkedDeleted(tableint internalId) const {
822 unsigned char *ll_cur = ((unsigned char*)get_linklist0(internalId))+2;
823 return *ll_cur & DELETE_MARK;
824 }
825
826 unsigned short int getListCount(linklistsizeint * ptr) const {
827 return *((unsigned short int *)ptr);
828 }
829
830 void setListCount(linklistsizeint * ptr, unsigned short int size) const {
831 *((unsigned short int*)(ptr))=*((unsigned short int *)&size);
832 }
833
834 void addPoint(const void *data_point, labeltype label) {
835 addPoint(data_point, label,-1);
836 }
837
838 void updatePoint(const void *dataPoint, tableint internalId, float updateNeighborProbability) {
839 // update the feature vector associated with existing point with new vector
840 memcpy(getDataByInternalId(internalId), dataPoint, data_size_);
841
842 int maxLevelCopy = maxlevel_;
843 tableint entryPointCopy = enterpoint_node_;
844 // If point to be updated is entry point and graph just contains single element then just return.
845 if (entryPointCopy == internalId && cur_element_count == 1)
846 return;
847
848 int elemLevel = element_levels_[internalId];
849 std::uniform_real_distribution<float> distribution(0.0, 1.0);
850 for (int layer = 0; layer <= elemLevel; layer++) {
851 std::unordered_set<tableint> sCand;
852 std::unordered_set<tableint> sNeigh;
853 std::vector<tableint> listOneHop = getConnectionsWithLock(internalId, layer);
854 if (listOneHop.size() == 0)
855 continue;
856
857 sCand.insert(internalId);
858
859 for (auto&& elOneHop : listOneHop) {
860 sCand.insert(elOneHop);
861
862 if (distribution(update_probability_generator_) > updateNeighborProbability)
863 continue;
864
865 sNeigh.insert(elOneHop);
866
867 std::vector<tableint> listTwoHop = getConnectionsWithLock(elOneHop, layer);
868 for (auto&& elTwoHop : listTwoHop) {
869 sCand.insert(elTwoHop);
870 }
871 }
872
873 for (auto&& neigh : sNeigh) {
874 // if (neigh == internalId)
875 // continue;
876
877 std::priority_queue<std::pair<dist_t, tableint>, std::vector<std::pair<dist_t, tableint>>, CompareByFirst> candidates;
878 size_t size = sCand.find(neigh) == sCand.end() ? sCand.size() : sCand.size() - 1; // sCand guaranteed to have size >= 1
879 size_t elementsToKeep = std::min(ef_construction_, size);
880 for (auto&& cand : sCand) {
881 if (cand == neigh)
882 continue;
883
885 if (candidates.size() < elementsToKeep) {
886 candidates.emplace(distance, cand);
887 } else {
888 if (distance < candidates.top().first) {
889 candidates.pop();
890 candidates.emplace(distance, cand);
891 }
892 }
893 }
894
895 // Retrieve neighbours using heuristic and set connections.
896 getNeighborsByHeuristic2(candidates, layer == 0 ? maxM0_ : maxM_);
897
898 {
899 std::unique_lock <std::mutex> lock(link_list_locks_[neigh]);
900 linklistsizeint *ll_cur;
901 ll_cur = get_linklist_at_level(neigh, layer);
902 size_t candSize = candidates.size();
903 setListCount(ll_cur, candSize);
904 tableint *data = (tableint *) (ll_cur + 1);
905 for (size_t idx = 0; idx < candSize; idx++) {
906 data[idx] = candidates.top().second;
907 candidates.pop();
908 }
909 }
910 }
911 }
912
913 repairConnectionsForUpdate(dataPoint, entryPointCopy, internalId, elemLevel, maxLevelCopy);
914 };
915
916 void repairConnectionsForUpdate(const void *dataPoint, tableint entryPointInternalId, tableint dataPointInternalId, int dataPointLevel, int maxLevel) {
917 tableint currObj = entryPointInternalId;
918 if (dataPointLevel < maxLevel) {
919 dist_t curdist = fstdistfunc_(dataPoint, getDataByInternalId(currObj), dist_func_param_);
920 for (int level = maxLevel; level > dataPointLevel; level--) {
921 bool changed = true;
922 while (changed) {
923 changed = false;
924 unsigned int *data;
925 std::unique_lock <std::mutex> lock(link_list_locks_[currObj]);
926 data = get_linklist_at_level(currObj,level);
927 int size = getListCount(data);
928 tableint *datal = (tableint *) (data + 1);
929#ifdef USE_SSE
930 _mm_prefetch(getDataByInternalId(*datal), _MM_HINT_T0);
931#endif
932 for (int i = 0; i < size; i++) {
933#ifdef USE_SSE
934 _mm_prefetch(getDataByInternalId(*(datal + i + 1)), _MM_HINT_T0);
935#endif
936 tableint cand = datal[i];
937 dist_t d = fstdistfunc_(dataPoint, getDataByInternalId(cand), dist_func_param_);
938 if (d < curdist) {
939 curdist = d;
940 currObj = cand;
941 changed = true;
942 }
943 }
944 }
945 }
946 }
947
948 if (dataPointLevel > maxLevel)
949 throw std::runtime_error("Level of item to be updated cannot be bigger than max level");
950
951 for (int level = dataPointLevel; level >= 0; level--) {
952 std::priority_queue<std::pair<dist_t, tableint>, std::vector<std::pair<dist_t, tableint>>, CompareByFirst> topCandidates = searchBaseLayer(
953 currObj, dataPoint, level);
954
955 std::priority_queue<std::pair<dist_t, tableint>, std::vector<std::pair<dist_t, tableint>>, CompareByFirst> filteredTopCandidates;
956 while (topCandidates.size() > 0) {
957 if (topCandidates.top().second != dataPointInternalId)
958 filteredTopCandidates.push(topCandidates.top());
959
960 topCandidates.pop();
961 }
962
963 // Since element_levels_ is being used to get `dataPointLevel`, there could be cases where `topCandidates` could just contains entry point itself.
964 // To prevent self loops, the `topCandidates` is filtered and thus can be empty.
965 if (filteredTopCandidates.size() > 0) {
966 bool epDeleted = isMarkedDeleted(entryPointInternalId);
967 if (epDeleted) {
968 filteredTopCandidates.emplace(fstdistfunc_(dataPoint, getDataByInternalId(entryPointInternalId), dist_func_param_), entryPointInternalId);
969 if (filteredTopCandidates.size() > ef_construction_)
970 filteredTopCandidates.pop();
971 }
972
973 currObj = mutuallyConnectNewElement(dataPoint, dataPointInternalId, filteredTopCandidates, level, true);
974 }
975 }
976 }
977
978 std::vector<tableint> getConnectionsWithLock(tableint internalId, int level) {
979 std::unique_lock <std::mutex> lock(link_list_locks_[internalId]);
980 unsigned int *data = get_linklist_at_level(internalId, level);
981 int size = getListCount(data);
982 std::vector<tableint> result(size);
983 tableint *ll = (tableint *) (data + 1);
984 memcpy(result.data(), ll,size * sizeof(tableint));
985 return result;
986 };
987
988 tableint addPoint(const void *data_point, labeltype label, int level) {
989
990 tableint cur_c = 0;
991 {
992 // Checking if the element with the same label already exists
993 // if so, updating it *instead* of creating a new element.
994 std::unique_lock <std::mutex> templock_curr(cur_element_count_guard_);
995 auto search = label_lookup_.find(label);
996 if (search != label_lookup_.end()) {
997 tableint existingInternalId = search->second;
998 templock_curr.unlock();
999
1000 std::unique_lock <std::mutex> lock_el_update(link_list_update_locks_[(existingInternalId & (max_update_element_locks - 1))]);
1001
1002 if (isMarkedDeleted(existingInternalId)) {
1003 unmarkDeletedInternal(existingInternalId);
1004 }
1005 updatePoint(data_point, existingInternalId, 1.0);
1006
1007 return existingInternalId;
1008 }
1009
1011 throw std::runtime_error("The number of elements exceeds the specified limit");
1012 };
1013
1014 cur_c = cur_element_count;
1016 label_lookup_[label] = cur_c;
1017 }
1018
1019 // Take update lock to prevent race conditions on an element with insertion/update at the same time.
1020 std::unique_lock <std::mutex> lock_el_update(link_list_update_locks_[(cur_c & (max_update_element_locks - 1))]);
1021 std::unique_lock <std::mutex> lock_el(link_list_locks_[cur_c]);
1022 int curlevel = getRandomLevel(mult_);
1023 if (level > 0)
1024 curlevel = level;
1025
1026 element_levels_[cur_c] = curlevel;
1027
1028
1029 std::unique_lock <std::mutex> templock(global);
1030 int maxlevelcopy = maxlevel_;
1031 if (curlevel <= maxlevelcopy)
1032 templock.unlock();
1033 tableint currObj = enterpoint_node_;
1034 tableint enterpoint_copy = enterpoint_node_;
1035
1036
1038
1039 // Initialisation of the data and label
1040 memcpy(getExternalLabeLp(cur_c), &label, sizeof(labeltype));
1041 memcpy(getDataByInternalId(cur_c), data_point, data_size_);
1042
1043
1044 if (curlevel) {
1045 linkLists_[cur_c] = (char *) malloc(size_links_per_element_ * curlevel + 1);
1046 if (linkLists_[cur_c] == nullptr)
1047 throw std::runtime_error("Not enough memory: addPoint failed to allocate linklist");
1048 memset(linkLists_[cur_c], 0, size_links_per_element_ * curlevel + 1);
1049 }
1050
1051 if ((signed)currObj != -1) {
1052
1053 if (curlevel < maxlevelcopy) {
1054
1055 dist_t curdist = fstdistfunc_(data_point, getDataByInternalId(currObj), dist_func_param_);
1056 for (int levelScope = maxlevelcopy; levelScope > curlevel; levelScope--) {
1057
1058
1059 bool changed = true;
1060 while (changed) {
1061 changed = false;
1062 unsigned int *data;
1063 std::unique_lock <std::mutex> lock(link_list_locks_[currObj]);
1064 data = get_linklist(currObj,levelScope);
1065 int size = getListCount(data);
1066
1067 tableint *datal = (tableint *) (data + 1);
1068 for (int i = 0; i < size; i++) {
1069 tableint cand = datal[i];
1070 if (cand < 0 || cand > max_elements_)
1071 throw std::runtime_error("cand error");
1072 dist_t d = fstdistfunc_(data_point, getDataByInternalId(cand), dist_func_param_);
1073 if (d < curdist) {
1074 curdist = d;
1075 currObj = cand;
1076 changed = true;
1077 }
1078 }
1079 }
1080 }
1081 }
1082
1083 bool epDeleted = isMarkedDeleted(enterpoint_copy);
1084 for (int levelScope2 = std::min(curlevel, maxlevelcopy); levelScope2 >= 0; levelScope2--) {
1085 if (levelScope2 > maxlevelcopy || levelScope2 < 0) // possible?
1086 throw std::runtime_error("Level error");
1087
1088 std::priority_queue<std::pair<dist_t, tableint>, std::vector<std::pair<dist_t, tableint>>, CompareByFirst> top_candidates = searchBaseLayer(
1089 currObj, data_point, levelScope2);
1090 if (epDeleted) {
1091 top_candidates.emplace(fstdistfunc_(data_point, getDataByInternalId(enterpoint_copy), dist_func_param_), enterpoint_copy);
1092 if (top_candidates.size() > ef_construction_)
1093 top_candidates.pop();
1094 }
1095 currObj = mutuallyConnectNewElement(data_point, cur_c, top_candidates, level, false);
1096 }
1097
1098
1099 } else {
1100 // Do nothing for the first element
1101 enterpoint_node_ = 0;
1102 maxlevel_ = curlevel;
1103
1104 }
1105
1106 //Releasing lock for the maximum level
1107 if (curlevel > maxlevelcopy) {
1108 enterpoint_node_ = cur_c;
1109 maxlevel_ = curlevel;
1110 }
1111 return cur_c;
1112 };
1113
1114 std::priority_queue<std::pair<dist_t, labeltype >>
1115 searchKnn(const void *query_data, size_t k) const {
1116 std::priority_queue<std::pair<dist_t, labeltype >> result;
1117 if (cur_element_count == 0) return result;
1118
1119 tableint currObj = enterpoint_node_;
1120 dist_t curdist = fstdistfunc_(query_data, getDataByInternalId(enterpoint_node_), dist_func_param_);
1121
1122 for (int level = maxlevel_; level > 0; level--) {
1123 bool changed = true;
1124 while (changed) {
1125 changed = false;
1126 unsigned int *data;
1127
1128 data = (unsigned int *) get_linklist(currObj, level);
1129 int size = getListCount(data);
1130 metric_hops++;
1132
1133 tableint *datal = (tableint *) (data + 1);
1134 for (int i = 0; i < size; i++) {
1135 tableint cand = datal[i];
1136 if (cand < 0 || cand > max_elements_)
1137 throw std::runtime_error("cand error");
1138 dist_t d = fstdistfunc_(query_data, getDataByInternalId(cand), dist_func_param_);
1139
1140 if (d < curdist) {
1141 curdist = d;
1142 currObj = cand;
1143 changed = true;
1144 }
1145 }
1146 }
1147 }
1148
1149 std::priority_queue<std::pair<dist_t, tableint>, std::vector<std::pair<dist_t, tableint>>, CompareByFirst> top_candidates;
1150 if (num_deleted_) {
1151 top_candidates=searchBaseLayerST<true,true>(
1152 currObj, query_data, std::max(ef_, k));
1153 }
1154 else{
1155 top_candidates=searchBaseLayerST<false,true>(
1156 currObj, query_data, std::max(ef_, k));
1157 }
1158
1159 while (top_candidates.size() > k) {
1160 top_candidates.pop();
1161 }
1162 while (top_candidates.size() > 0) {
1163 std::pair<dist_t, tableint> rez = top_candidates.top();
1164 result.push(std::pair<dist_t, labeltype>(rez.first, getExternalLabel(rez.second)));
1165 top_candidates.pop();
1166 }
1167 return result;
1168 };
1169
1171 int connections_checked=0;
1172 std::vector <int > inbound_connections_num(cur_element_count,0);
1173 for(int i = 0;i < cur_element_count; i++){
1174 for(int l = 0;l <= element_levels_[i]; l++){
1176 int size = getListCount(ll_cur);
1177 tableint *data = (tableint *) (ll_cur + 1);
1178 std::unordered_set<tableint> s;
1179 for (int j=0; j<size; j++){
1180 assert(data[j] > 0);
1181 assert(data[j] < cur_element_count);
1182 assert (data[j] != i);
1183 inbound_connections_num[data[j]]++;
1184 s.insert(data[j]);
1185 connections_checked++;
1186
1187 }
1188 assert(s.size() == size);
1189 }
1190 }
1191 if(cur_element_count > 1){
1192 int min1=inbound_connections_num[0], max1=inbound_connections_num[0];
1193 for(int i=0; i < cur_element_count; i++){
1194 assert(inbound_connections_num[i] > 0);
1195 min1=std::min(inbound_connections_num[i],min1);
1196 max1=std::max(inbound_connections_num[i],max1);
1197 }
1198 std::cout << "Min inbound: " << min1 << ", Max inbound:" << max1 << "\n";
1199 }
1200 std::cout << "integrity ok, checked " << connections_checked << " connections\n";
1201
1202 }
1203
1204 };
1205
1206}
Definition hnswlib.h:155
Definition hnswalg.h:17
char * data_level0_memory_
Definition hnswalg.h:116
size_t maxM0_
Definition hnswalg.h:96
linklistsizeint * get_linklist(tableint internal_id, int level) const
Definition hnswalg.h:379
size_t size_data_per_element_
Definition hnswalg.h:90
size_t offsetLevel0_
Definition hnswalg.h:114
static const unsigned char DELETE_MARK
Definition hnswalg.h:749
std::vector< int > element_levels_
Definition hnswalg.h:118
~HierarchicalNSW()
Definition hnswalg.h:77
double mult_
Definition hnswalg.h:99
std::priority_queue< std::pair< dist_t, tableint >, std::vector< std::pair< dist_t, tableint > >, CompareByFirst > searchBaseLayerST(tableint ep_id, const void *data_point, size_t ef) const
Definition hnswalg.h:242
unsigned short int getListCount(linklistsizeint *ptr) const
Definition hnswalg.h:826
std::atomic< long > metric_distance_computations
Definition hnswalg.h:237
size_t maxM_
Definition hnswalg.h:95
size_t label_offset_
Definition hnswalg.h:122
labeltype * getExternalLabeLp(tableint internal_id) const
Definition hnswalg.h:140
labeltype getExternalLabel(tableint internal_id) const
Definition hnswalg.h:130
std::atomic< long > metric_hops
Definition hnswalg.h:238
void saveIndex(const std::string &location)
Definition hnswalg.h:588
tableint mutuallyConnectNewElement(const void *, tableint cur_c, std::priority_queue< std::pair< dist_t, tableint >, std::vector< std::pair< dist_t, tableint > >, CompareByFirst > &top_candidates, int level, bool isUpdate)
Definition hnswalg.h:387
std::vector< std::mutex > link_list_update_locks_
Definition hnswalg.h:110
std::vector< tableint > getConnectionsWithLock(tableint internalId, int level)
Definition hnswalg.h:978
size_t data_size_
Definition hnswalg.h:120
std::priority_queue< std::pair< dist_t, labeltype > > searchKnn(const void *query_data, size_t k) const
Definition hnswalg.h:1115
void markDelete(labeltype label)
Definition hnswalg.h:755
std::vector< std::mutex > link_list_locks_
Definition hnswalg.h:106
void * dist_func_param_
Definition hnswalg.h:124
size_t max_elements_
Definition hnswalg.h:88
void unmarkDeletedInternal(tableint internalId)
Definition hnswalg.h:802
size_t offsetData_
Definition hnswalg.h:114
size_t ef_construction_
Definition hnswalg.h:97
VisitedListPool * visited_list_pool_
Definition hnswalg.h:103
static const tableint max_update_element_locks
Definition hnswalg.h:19
char * getDataByInternalId(tableint internal_id) const
Definition hnswalg.h:144
void repairConnectionsForUpdate(const void *dataPoint, tableint entryPointInternalId, tableint dataPointInternalId, int dataPointLevel, int maxLevel)
Definition hnswalg.h:916
size_t cur_element_count
Definition hnswalg.h:89
std::default_random_engine level_generator_
Definition hnswalg.h:127
void resizeIndex(size_t new_max_elements)
Definition hnswalg.h:560
HierarchicalNSW(SpaceInterface< dist_t > *s, const std::string &location, bool nmslib=false, size_t max_elements=0)
Definition hnswalg.h:23
char ** linkLists_
Definition hnswalg.h:117
std::mutex cur_element_count_guard_
Definition hnswalg.h:104
std::mutex global
Definition hnswalg.h:506
std::priority_queue< std::pair< dist_t, tableint > > searchKnnInternal(void *query_data, int k)
Definition hnswalg.h:514
int maxlevel_
Definition hnswalg.h:100
std::unordered_map< labeltype, tableint > label_lookup_
Definition hnswalg.h:125
DISTFUNC< dist_t > fstdistfunc_
Definition hnswalg.h:123
size_t size_links_per_element_
Definition hnswalg.h:91
std::default_random_engine update_probability_generator_
Definition hnswalg.h:128
void updatePoint(const void *dataPoint, tableint internalId, float updateNeighborProbability)
Definition hnswalg.h:838
size_t num_deleted_
Definition hnswalg.h:92
int getRandomLevel(double reverse_size)
Definition hnswalg.h:148
void loadIndex(const std::string &location, SpaceInterface< dist_t > *s, bool, size_t max_elements_i=0)
Definition hnswalg.h:618
HierarchicalNSW(SpaceInterface< dist_t > *)
Definition hnswalg.h:20
tableint enterpoint_node_
Definition hnswalg.h:111
void addPoint(const void *data_point, labeltype label)
Definition hnswalg.h:834
linklistsizeint * get_linklist0(tableint internal_id) const
Definition hnswalg.h:371
HierarchicalNSW(SpaceInterface< dist_t > *s, size_t max_elements, size_t M=16, size_t ef_construction=200, size_t random_seed=100)
Definition hnswalg.h:27
std::priority_queue< std::pair< dist_t, tableint >, std::vector< std::pair< dist_t, tableint > >, CompareByFirst > searchBaseLayer(tableint ep_id, const void *data_point, int layer)
Definition hnswalg.h:156
size_t ef_
Definition hnswalg.h:507
size_t M_
Definition hnswalg.h:94
void getNeighborsByHeuristic2(std::priority_queue< std::pair< dist_t, tableint >, std::vector< std::pair< dist_t, tableint > >, CompareByFirst > &top_candidates, const size_t M)
Definition hnswalg.h:328
size_t size_links_level0_
Definition hnswalg.h:113
void markDeletedInternal(tableint internalId)
Definition hnswalg.h:770
void unmarkDelete(labeltype label)
Definition hnswalg.h:788
void setEf(size_t ef)
Definition hnswalg.h:509
void checkIntegrity()
Definition hnswalg.h:1170
void setExternalLabel(tableint internal_id, labeltype label) const
Definition hnswalg.h:136
double revSize_
Definition hnswalg.h:99
void setListCount(linklistsizeint *ptr, unsigned short int size) const
Definition hnswalg.h:830
std::vector< data_t > getDataByLabel(labeltype label) const
Definition hnswalg.h:729
linklistsizeint * get_linklist_at_level(tableint internal_id, int level) const
Definition hnswalg.h:383
tableint addPoint(const void *data_point, labeltype label, int level)
Definition hnswalg.h:988
bool isMarkedDeleted(tableint internalId) const
Definition hnswalg.h:821
Definition hnswlib.h:142
virtual size_t get_data_size()=0
virtual void * get_dist_func_param()=0
virtual DISTFUNC< MTYPE > get_dist_func()=0
Definition visited_list_pool.h:10
vl_type * mass
Definition visited_list_pool.h:13
vl_type curV
Definition visited_list_pool.h:12
Definition visited_list_pool.h:38
void releaseVisitedList(VisitedList *vl)
Definition visited_list_pool.h:65
VisitedList * getFreeVisitedList()
Definition visited_list_pool.h:50
Definition bruteforce.h:7
unsigned int linklistsizeint
Definition hnswalg.h:14
unsigned short int vl_type
Definition visited_list_pool.h:8
size_t labeltype
Definition hnswlib.h:117
static void writeBinaryPOD(std::ostream &out, const T &podRef)
Definition hnswlib.h:128
unsigned int tableint
Definition hnswalg.h:13
static void readBinaryPOD(std::istream &in, T &podRef)
Definition hnswlib.h:133
MTYPE(*)(const void *, const void *, const void *) DISTFUNC
Definition hnswlib.h:138
constexpr bool operator()(std::pair< dist_t, tableint > const &a, std::pair< dist_t, tableint > const &b) const noexcept
Definition hnswalg.h:71