usearch 0.9.7 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yml +1 -1
- package/.gitmodules +3 -0
- package/.vscode/settings.json +2 -0
- package/Cargo.toml +1 -1
- package/Package.swift +1 -0
- package/README.md +1 -1
- package/VERSION +1 -1
- package/binding.gyp +4 -3
- package/build.gradle +1 -1
- package/build.rs +1 -0
- package/conanfile.py +1 -1
- package/docs/compilation.md +6 -3
- package/include/usearch/usearch.hpp +168 -45
- package/package.json +1 -1
- package/python/lib.cpp +42 -23
- package/python/scripts/bench.py +2 -2
- package/python/scripts/test.py +1 -1
- package/python/usearch/index.py +298 -0
- package/robin-map/.codecov.yml +5 -0
- package/robin-map/.github/workflows/ci.yml +136 -0
- package/robin-map/CMakeLists.txt +86 -0
- package/robin-map/LICENSE +21 -0
- package/robin-map/README.md +483 -0
- package/robin-map/cmake/tsl-robin-mapConfig.cmake.in +9 -0
- package/robin-map/doxygen.conf +2483 -0
- package/robin-map/include/tsl/robin_growth_policy.h +406 -0
- package/robin-map/include/tsl/robin_hash.h +1639 -0
- package/robin-map/include/tsl/robin_map.h +807 -0
- package/robin-map/include/tsl/robin_set.h +660 -0
- package/robin-map/tests/CMakeLists.txt +26 -0
- package/robin-map/tests/custom_allocator_tests.cpp +138 -0
- package/robin-map/tests/main.cpp +26 -0
- package/robin-map/tests/policy_tests.cpp +97 -0
- package/robin-map/tests/robin_map_tests.cpp +1451 -0
- package/robin-map/tests/robin_set_tests.cpp +164 -0
- package/robin-map/tests/utils.h +443 -0
- package/robin-map/tsl-robin-map.natvis +78 -0
- package/setup.py +4 -2
- package/src/CMakeLists.txt +1 -0
- package/src/punned.hpp +56 -6
- package/src/test.cpp +32 -8
package/.gitmodules
CHANGED
package/.vscode/settings.json
CHANGED
|
@@ -125,10 +125,12 @@
|
|
|
125
125
|
"OPENMP",
|
|
126
126
|
"preprocess",
|
|
127
127
|
"Quickstart",
|
|
128
|
+
"rtype",
|
|
128
129
|
"simsimd",
|
|
129
130
|
"SLOC",
|
|
130
131
|
"usearch",
|
|
131
132
|
"usecases"
|
|
132
133
|
],
|
|
134
|
+
"autoDocstring.docstringFormat": "sphinx",
|
|
133
135
|
"java.configuration.updateBuildConfiguration": "interactive"
|
|
134
136
|
}
|
package/Cargo.toml
CHANGED
package/Package.swift
CHANGED
package/README.md
CHANGED
|
@@ -308,7 +308,7 @@ def python_dot(a, b, n, m):
|
|
|
308
308
|
c += a_array[i] * b_array[i]
|
|
309
309
|
return c
|
|
310
310
|
|
|
311
|
-
index = Index(ndim=ndim,
|
|
311
|
+
index = Index(ndim=ndim, metric=python_dot.address)
|
|
312
312
|
```
|
|
313
313
|
|
|
314
314
|
[numba]: https://numba.readthedocs.io/en/stable/reference/jit-compilation.html#c-callbacks
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.11.0
|
package/binding.gyp
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"include",
|
|
11
11
|
"src",
|
|
12
12
|
"fp16/include",
|
|
13
|
+
"robin-map/include",
|
|
13
14
|
"simsimd/include"
|
|
14
15
|
],
|
|
15
16
|
"dependencies": [
|
|
@@ -23,9 +24,9 @@
|
|
|
23
24
|
"MACOSX_DEPLOYMENT_TARGET": "10.15"
|
|
24
25
|
},
|
|
25
26
|
"msvs_settings": {
|
|
26
|
-
"VCCLCompilerTool": {
|
|
27
|
-
"ExceptionHandling": 1,
|
|
28
|
-
"AdditionalOptions": [
|
|
27
|
+
"VCCLCompilerTool": {
|
|
28
|
+
"ExceptionHandling": 1,
|
|
29
|
+
"AdditionalOptions": ["-std:c++11"]
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
}
|
package/build.gradle
CHANGED
|
@@ -45,7 +45,7 @@ model {
|
|
|
45
45
|
include "**/*.cpp"
|
|
46
46
|
}
|
|
47
47
|
exportedHeaders {
|
|
48
|
-
srcDirs "include", "src", "fp16/include", "simsimd/include", "${Jvm.current().javaHome}/include"
|
|
48
|
+
srcDirs "include", "src", "fp16/include", "robin-map/include", "simsimd/include", "${Jvm.current().javaHome}/include"
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
}
|
package/build.rs
CHANGED
package/conanfile.py
CHANGED
|
@@ -4,7 +4,7 @@ from conans import ConanFile
|
|
|
4
4
|
class USearchConan(ConanFile):
|
|
5
5
|
|
|
6
6
|
name = 'USearch'
|
|
7
|
-
version = '0.
|
|
7
|
+
version = '0.11.0'
|
|
8
8
|
license = 'Apache 2.0'
|
|
9
9
|
url = 'https://github.com/unum-cloud/usearch'
|
|
10
10
|
description = 'Smaller & Faster Single-File Vector Search Engine from Unum'
|
package/docs/compilation.md
CHANGED
|
@@ -63,8 +63,7 @@ Benchmarking:
|
|
|
63
63
|
## Python
|
|
64
64
|
|
|
65
65
|
```sh
|
|
66
|
-
pip install -e .
|
|
67
|
-
pytest python/scripts/test.py
|
|
66
|
+
pip install -e . && pytest python/scripts/test.py
|
|
68
67
|
```
|
|
69
68
|
|
|
70
69
|
Linting:
|
|
@@ -131,7 +130,11 @@ g++ -shared -o USearchJNI.dll cloud_unum_usearch_Index.o -Wl,--add-stdcall-alias
|
|
|
131
130
|
|
|
132
131
|
# MacOS
|
|
133
132
|
g++ -std=c++11 -c -fPIC \
|
|
134
|
-
-I../../../../include
|
|
133
|
+
-I../../../../include \
|
|
134
|
+
-I../../../../src \
|
|
135
|
+
-I../../../../fp16/include \
|
|
136
|
+
-I../../../../robin-map/include \
|
|
137
|
+
-I../../../../simsimd/include \
|
|
135
138
|
-I${JAVA_HOME}/include -I${JAVA_HOME}/include/darwin cloud_unum_usearch_Index.cpp -o cloud_unum_usearch_Index.o
|
|
136
139
|
g++ -dynamiclib -o libusearch.dylib cloud_unum_usearch_Index.o -lc
|
|
137
140
|
# Run linking to that directory
|
|
@@ -9,6 +9,18 @@
|
|
|
9
9
|
#ifndef UNUM_USEARCH_H
|
|
10
10
|
#define UNUM_USEARCH_H
|
|
11
11
|
|
|
12
|
+
#if !defined(USEARCH_VERSION_MAJOR)
|
|
13
|
+
#define USEARCH_VERSION_MAJOR 0
|
|
14
|
+
#endif
|
|
15
|
+
|
|
16
|
+
#if !defined(USEARCH_VERSION_MINOR)
|
|
17
|
+
#define USEARCH_VERSION_MINOR 0
|
|
18
|
+
#endif
|
|
19
|
+
|
|
20
|
+
#if !defined(USEARCH_VERSION_PATCH)
|
|
21
|
+
#define USEARCH_VERSION_PATCH 0
|
|
22
|
+
#endif
|
|
23
|
+
|
|
12
24
|
// Inferring C++ version
|
|
13
25
|
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
|
|
14
26
|
#define USEARCH_IS_CPP17
|
|
@@ -61,7 +73,7 @@
|
|
|
61
73
|
#include <mutex> // `std::unique_lock` - replacement candidate
|
|
62
74
|
#include <random> // `std::default_random_engine` - replacement candidate
|
|
63
75
|
#include <stdexcept> // `std::runtime_exception`
|
|
64
|
-
#include <utility> // `std::
|
|
76
|
+
#include <utility> // `std::pair`
|
|
65
77
|
#include <vector> // `std::vector`
|
|
66
78
|
|
|
67
79
|
// Prefetching
|
|
@@ -125,12 +137,20 @@ inline std::size_t ceil2(std::size_t v) noexcept {
|
|
|
125
137
|
|
|
126
138
|
template <typename at> void misaligned_store(void* ptr, at v) noexcept { std::memcpy(ptr, &v, sizeof(v)); }
|
|
127
139
|
|
|
140
|
+
/// @brief Simply dereferencing misaligned pointers can be dangerous.
|
|
128
141
|
template <typename at> at misaligned_load(void* ptr) noexcept {
|
|
129
142
|
at v;
|
|
130
143
|
std::memcpy(&v, ptr, sizeof(v));
|
|
131
144
|
return v;
|
|
132
145
|
}
|
|
133
146
|
|
|
147
|
+
/// @brief The `std::exchange` alternative for C++11.
|
|
148
|
+
template <typename at, typename other_at = at> at exchange(at& obj, other_at&& new_value) {
|
|
149
|
+
at old_value = std::move(obj);
|
|
150
|
+
obj = std::forward<other_at>(new_value);
|
|
151
|
+
return old_value;
|
|
152
|
+
}
|
|
153
|
+
|
|
134
154
|
template <typename at> class misaligned_ref_gt {
|
|
135
155
|
byte_t* ptr_;
|
|
136
156
|
|
|
@@ -141,10 +161,8 @@ template <typename at> class misaligned_ref_gt {
|
|
|
141
161
|
misaligned_store<at>(ptr_, v);
|
|
142
162
|
return *this;
|
|
143
163
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return *this;
|
|
147
|
-
}
|
|
164
|
+
|
|
165
|
+
void reset(byte_t* ptr) noexcept { ptr_ = ptr; }
|
|
148
166
|
};
|
|
149
167
|
|
|
150
168
|
template <typename at> class misaligned_ptr_gt {
|
|
@@ -781,6 +799,13 @@ template <typename scalar_at> class span_gt {
|
|
|
781
799
|
constexpr std::size_t default_connectivity() { return 16; }
|
|
782
800
|
constexpr std::size_t default_expansion_add() { return 128; }
|
|
783
801
|
constexpr std::size_t default_expansion_search() { return 64; }
|
|
802
|
+
constexpr std::size_t default_allocator_entry_bytes() { return 64; }
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* @brief The "magic" sequence helps infer the type of the file.
|
|
806
|
+
* USearch indexes start with the "unumusearch" string.
|
|
807
|
+
*/
|
|
808
|
+
static constexpr char const* default_magic() { return "unumusearch"; }
|
|
784
809
|
|
|
785
810
|
/**
|
|
786
811
|
* @brief Configuration settings for the index construction.
|
|
@@ -808,6 +833,7 @@ struct config_t {
|
|
|
808
833
|
std::size_t max_elements = 0;
|
|
809
834
|
std::size_t max_threads_add = 1;
|
|
810
835
|
std::size_t max_threads_search = 1;
|
|
836
|
+
std::size_t vector_alignment = 1;
|
|
811
837
|
|
|
812
838
|
std::size_t max_threads() const noexcept { return (std::max)(max_threads_add, max_threads_search); }
|
|
813
839
|
std::size_t concurrency() const noexcept { return (std::min)(max_threads_add, max_threads_search); }
|
|
@@ -825,6 +851,67 @@ struct search_config_t {
|
|
|
825
851
|
bool exact{};
|
|
826
852
|
};
|
|
827
853
|
|
|
854
|
+
using file_header_t = byte_t[64];
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* @brief Serialized binary representations of the USearch index start with metadata.
|
|
858
|
+
* Metadata is parsed into a `file_head_t`, containing the USearch package version,
|
|
859
|
+
* and the properties of the index.
|
|
860
|
+
*/
|
|
861
|
+
struct file_head_t {
|
|
862
|
+
|
|
863
|
+
using magic_t = char[11];
|
|
864
|
+
using version_major_t = std::uint16_t;
|
|
865
|
+
using version_minor_t = std::uint16_t;
|
|
866
|
+
using version_patch_t = std::uint16_t;
|
|
867
|
+
|
|
868
|
+
enum metric_t : std::uint8_t {
|
|
869
|
+
unknown_k = 0,
|
|
870
|
+
ip_k = 'i',
|
|
871
|
+
cos_k = 'c',
|
|
872
|
+
l2sq_k = 'e',
|
|
873
|
+
jaccard_k = 'j',
|
|
874
|
+
hamming_k = 'h',
|
|
875
|
+
pearson_k = 'p',
|
|
876
|
+
haversine_k = 'h',
|
|
877
|
+
};
|
|
878
|
+
|
|
879
|
+
using connectivity_t = std::uint8_t;
|
|
880
|
+
using max_level_t = std::uint8_t;
|
|
881
|
+
using vector_alignment_t = std::uint8_t;
|
|
882
|
+
using bytes_per_label_t = std::uint16_t;
|
|
883
|
+
using bytes_per_id_t = std::uint16_t;
|
|
884
|
+
using size_t = std::uint64_t;
|
|
885
|
+
using entry_idx_t = std::uint64_t;
|
|
886
|
+
|
|
887
|
+
char const* magic;
|
|
888
|
+
misaligned_ref_gt<version_major_t> version_major;
|
|
889
|
+
misaligned_ref_gt<version_minor_t> version_minor;
|
|
890
|
+
misaligned_ref_gt<version_patch_t> version_patch;
|
|
891
|
+
misaligned_ref_gt<metric_t> metric;
|
|
892
|
+
misaligned_ref_gt<connectivity_t> connectivity;
|
|
893
|
+
misaligned_ref_gt<max_level_t> max_level;
|
|
894
|
+
misaligned_ref_gt<vector_alignment_t> vector_alignment;
|
|
895
|
+
misaligned_ref_gt<bytes_per_label_t> bytes_per_label;
|
|
896
|
+
misaligned_ref_gt<bytes_per_id_t> bytes_per_id;
|
|
897
|
+
misaligned_ref_gt<size_t> size;
|
|
898
|
+
misaligned_ref_gt<entry_idx_t> entry_idx;
|
|
899
|
+
|
|
900
|
+
file_head_t(byte_t* ptr) noexcept
|
|
901
|
+
: magic((char const*)exchange(ptr, ptr + sizeof(magic_t))),
|
|
902
|
+
version_major(exchange(ptr, ptr + sizeof(version_major_t))),
|
|
903
|
+
version_minor(exchange(ptr, ptr + sizeof(version_minor_t))),
|
|
904
|
+
version_patch(exchange(ptr, ptr + sizeof(version_patch_t))), metric(exchange(ptr, ptr + sizeof(metric_t))),
|
|
905
|
+
connectivity(exchange(ptr, ptr + sizeof(connectivity_t))),
|
|
906
|
+
max_level(exchange(ptr, ptr + sizeof(max_level_t))),
|
|
907
|
+
vector_alignment(exchange(ptr, ptr + sizeof(vector_alignment_t))),
|
|
908
|
+
bytes_per_label(exchange(ptr, ptr + sizeof(bytes_per_label_t))),
|
|
909
|
+
bytes_per_id(exchange(ptr, ptr + sizeof(bytes_per_id_t))), size(exchange(ptr, ptr + sizeof(size_t))),
|
|
910
|
+
entry_idx(exchange(ptr, ptr + sizeof(entry_idx_t))) {}
|
|
911
|
+
};
|
|
912
|
+
|
|
913
|
+
static_assert(sizeof(file_header_t) == 64, "File header should be exactly 64 bytes");
|
|
914
|
+
|
|
828
915
|
/**
|
|
829
916
|
* @brief Approximate Nearest Neighbors Search index using the
|
|
830
917
|
* Hierarchical Navigable Small World graph algorithm.
|
|
@@ -916,14 +1003,15 @@ class index_gt {
|
|
|
916
1003
|
vector_view_t vector;
|
|
917
1004
|
};
|
|
918
1005
|
|
|
919
|
-
template <typename ref_at> class member_iterator_gt {
|
|
1006
|
+
template <typename ref_at, typename index_at> class member_iterator_gt {
|
|
920
1007
|
using ref_t = ref_at;
|
|
921
|
-
|
|
1008
|
+
using index_t = index_at;
|
|
1009
|
+
index_t* index_{};
|
|
922
1010
|
std::size_t offset_{};
|
|
923
1011
|
|
|
924
1012
|
friend class index_gt;
|
|
925
1013
|
member_iterator_gt() noexcept {}
|
|
926
|
-
member_iterator_gt(
|
|
1014
|
+
member_iterator_gt(index_t* index, std::size_t offset) noexcept : index_(index), offset_(offset) {}
|
|
927
1015
|
|
|
928
1016
|
public:
|
|
929
1017
|
using iterator_category = std::random_access_iterator_tag;
|
|
@@ -932,7 +1020,7 @@ class index_gt {
|
|
|
932
1020
|
using pointer = void;
|
|
933
1021
|
using reference = ref_t;
|
|
934
1022
|
|
|
935
|
-
reference operator*() const noexcept { return index_->node_with_id_(offset_)
|
|
1023
|
+
reference operator*() const noexcept { return ref_t(index_->node_with_id_(offset_)); }
|
|
936
1024
|
member_iterator_gt operator++(int) noexcept { return member_iterator_gt(index_, offset_ + 1); }
|
|
937
1025
|
member_iterator_gt operator--(int) noexcept { return member_iterator_gt(index_, offset_ - 1); }
|
|
938
1026
|
member_iterator_gt operator+(difference_type d) noexcept { return member_iterator_gt(index_, offset_ + d); }
|
|
@@ -962,8 +1050,8 @@ class index_gt {
|
|
|
962
1050
|
}
|
|
963
1051
|
};
|
|
964
1052
|
|
|
965
|
-
using member_iterator_t = member_iterator_gt<member_ref_t>;
|
|
966
|
-
using member_citerator_t = member_iterator_gt<member_cref_t>;
|
|
1053
|
+
using member_iterator_t = member_iterator_gt<member_ref_t, index_gt>;
|
|
1054
|
+
using member_citerator_t = member_iterator_gt<member_cref_t, index_gt const>;
|
|
967
1055
|
|
|
968
1056
|
// STL compatibility:
|
|
969
1057
|
using value_type = std::pair<label_t, distance_t>;
|
|
@@ -1050,8 +1138,8 @@ class index_gt {
|
|
|
1050
1138
|
|
|
1051
1139
|
operator vector_view_t() const noexcept { return {vector(), dim()}; }
|
|
1052
1140
|
vector_view_t vector_view() const noexcept { return {vector(), dim()}; }
|
|
1053
|
-
member_ref_t
|
|
1054
|
-
member_cref_t
|
|
1141
|
+
explicit operator member_ref_t() noexcept { return {{tape_}, vector_view()}; }
|
|
1142
|
+
explicit operator member_cref_t() const noexcept { return {label(), vector_view()}; }
|
|
1055
1143
|
};
|
|
1056
1144
|
|
|
1057
1145
|
/**
|
|
@@ -1273,7 +1361,7 @@ class index_gt {
|
|
|
1273
1361
|
inline std::size_t size() const noexcept { return count; }
|
|
1274
1362
|
inline search_result_t operator[](std::size_t i) const noexcept {
|
|
1275
1363
|
candidate_t const* top_ordered = top_.data();
|
|
1276
|
-
return {index_.node_with_id_(top_ordered[i].id)
|
|
1364
|
+
return {member_cref_t(index_.node_with_id_(top_ordered[i].id)), top_ordered[i].distance};
|
|
1277
1365
|
}
|
|
1278
1366
|
inline std::size_t dump_to(label_t* labels, distance_t* distances) const noexcept {
|
|
1279
1367
|
for (std::size_t i = 0; i != count; ++i) {
|
|
@@ -1416,6 +1504,7 @@ class index_gt {
|
|
|
1416
1504
|
std::size_t nodes;
|
|
1417
1505
|
std::size_t edges;
|
|
1418
1506
|
std::size_t max_edges;
|
|
1507
|
+
std::size_t allocated_bytes;
|
|
1419
1508
|
};
|
|
1420
1509
|
|
|
1421
1510
|
stats_t stats() const noexcept {
|
|
@@ -1428,6 +1517,7 @@ class index_gt {
|
|
|
1428
1517
|
for (level_t level = 0; level != node.level(); ++level)
|
|
1429
1518
|
edges += neighbors_(node, level).size();
|
|
1430
1519
|
|
|
1520
|
+
result.allocated_bytes += node_bytes_(node);
|
|
1431
1521
|
result.edges += edges;
|
|
1432
1522
|
result.max_edges += max_edges;
|
|
1433
1523
|
}
|
|
@@ -1437,34 +1527,55 @@ class index_gt {
|
|
|
1437
1527
|
stats_t stats(std::size_t level) const noexcept {
|
|
1438
1528
|
stats_t result{};
|
|
1439
1529
|
result.nodes = size();
|
|
1530
|
+
|
|
1531
|
+
std::size_t neighbors_bytes = !level ? pre_.neighbors_base_bytes : pre_.neighbors_bytes;
|
|
1440
1532
|
for (std::size_t i = 0; i != result.nodes; ++i) {
|
|
1441
1533
|
node_t node = node_with_id_(i);
|
|
1442
|
-
if (node.level()
|
|
1443
|
-
|
|
1534
|
+
if (node.level() < level)
|
|
1535
|
+
continue;
|
|
1536
|
+
|
|
1537
|
+
result.edges += neighbors_(node, level).size();
|
|
1538
|
+
result.allocated_bytes += node_head_bytes_() + node_vector_bytes_(node) + neighbors_bytes;
|
|
1444
1539
|
}
|
|
1540
|
+
|
|
1445
1541
|
std::size_t max_edges_per_node = level ? config_.connectivity : base_level_multiple_() * config_.connectivity;
|
|
1446
1542
|
result.max_edges = result.nodes * max_edges_per_node;
|
|
1447
1543
|
return result;
|
|
1448
1544
|
}
|
|
1449
1545
|
|
|
1450
|
-
|
|
1546
|
+
/**
|
|
1547
|
+
* @brief A relatively accurate lower bound on the amount of memory consumed by the system.
|
|
1548
|
+
* In practice it's error will be below 10%.
|
|
1549
|
+
*/
|
|
1550
|
+
std::size_t memory_usage(std::size_t allocator_entry_bytes = default_allocator_entry_bytes()) const noexcept {
|
|
1551
|
+
std::size_t total = 0;
|
|
1552
|
+
if (viewed_file_descriptor_ == 0) {
|
|
1553
|
+
stats_t s = stats();
|
|
1554
|
+
total += s.allocated_bytes;
|
|
1555
|
+
total += s.nodes * allocator_entry_bytes;
|
|
1556
|
+
}
|
|
1451
1557
|
|
|
1452
|
-
|
|
1558
|
+
// Temporary data-structures, proportional to the number of nodes:
|
|
1559
|
+
total += nodes_.capacity() * sizeof(node_t) + allocator_entry_bytes;
|
|
1453
1560
|
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1561
|
+
// Temporary data-structures, proportional to the number of threads:
|
|
1562
|
+
std::size_t per_thread = (std::max)(config_.expansion_search, config_.expansion_add) * sizeof(candidate_t);
|
|
1563
|
+
total += thread_contexts_.capacity() * (sizeof(thread_context_t) + per_thread) + allocator_entry_bytes * 3;
|
|
1564
|
+
return total;
|
|
1565
|
+
}
|
|
1459
1566
|
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1567
|
+
void change_expansion_add(std::size_t n) noexcept { config_.expansion_add = n; }
|
|
1568
|
+
void change_expansion_search(std::size_t n) noexcept { config_.expansion_search = n; }
|
|
1569
|
+
void change_metric(metric_t const& m) noexcept(false) {
|
|
1570
|
+
metric_ = m;
|
|
1571
|
+
for (thread_context_t& context : thread_contexts_)
|
|
1572
|
+
context.metric = metric_;
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
#pragma endregion
|
|
1576
|
+
|
|
1577
|
+
#pragma region Serialization
|
|
1466
1578
|
|
|
1467
|
-
public:
|
|
1468
1579
|
/**
|
|
1469
1580
|
* @brief Saves serialized binary index representation to disk,
|
|
1470
1581
|
* co-locating vectors and neighbors lists.
|
|
@@ -1472,15 +1583,25 @@ class index_gt {
|
|
|
1472
1583
|
*/
|
|
1473
1584
|
void save(char const* file_path) const noexcept(false) {
|
|
1474
1585
|
|
|
1475
|
-
|
|
1586
|
+
file_header_t state_buffer{};
|
|
1587
|
+
std::memset(state_buffer, 0, sizeof(file_header_t));
|
|
1588
|
+
file_head_t state{state_buffer};
|
|
1589
|
+
std::memcpy(state_buffer, default_magic(), std::strlen(default_magic()));
|
|
1590
|
+
|
|
1476
1591
|
// Check compatibility
|
|
1477
|
-
state.
|
|
1478
|
-
state.
|
|
1592
|
+
state.version_major = USEARCH_VERSION_MAJOR;
|
|
1593
|
+
state.version_minor = USEARCH_VERSION_MINOR;
|
|
1594
|
+
state.version_patch = USEARCH_VERSION_PATCH;
|
|
1595
|
+
state.metric = file_head_t::unknown_k;
|
|
1596
|
+
|
|
1479
1597
|
// Describe state
|
|
1480
1598
|
state.connectivity = config_.connectivity;
|
|
1481
|
-
state.size = size_;
|
|
1482
|
-
state.entry_id = entry_id_;
|
|
1483
1599
|
state.max_level = max_level_;
|
|
1600
|
+
state.vector_alignment = config_.vector_alignment;
|
|
1601
|
+
state.bytes_per_label = sizeof(label_t);
|
|
1602
|
+
state.bytes_per_id = sizeof(id_t);
|
|
1603
|
+
state.size = size_;
|
|
1604
|
+
state.entry_idx = entry_id_;
|
|
1484
1605
|
|
|
1485
1606
|
std::FILE* file = std::fopen(file_path, "w");
|
|
1486
1607
|
if (!file)
|
|
@@ -1495,7 +1616,7 @@ class index_gt {
|
|
|
1495
1616
|
};
|
|
1496
1617
|
|
|
1497
1618
|
// Write the header
|
|
1498
|
-
write_chunk(&
|
|
1619
|
+
write_chunk(&state_buffer[0], sizeof(file_header_t));
|
|
1499
1620
|
|
|
1500
1621
|
// Serialize nodes one by one
|
|
1501
1622
|
for (std::size_t i = 0; i != state.size; ++i) {
|
|
@@ -1516,7 +1637,7 @@ class index_gt {
|
|
|
1516
1637
|
* Available on Linux, MacOS, Windows.
|
|
1517
1638
|
*/
|
|
1518
1639
|
void load(char const* file_path) noexcept(false) {
|
|
1519
|
-
|
|
1640
|
+
file_header_t state_buffer{};
|
|
1520
1641
|
std::FILE* file = std::fopen(file_path, "r");
|
|
1521
1642
|
if (!file)
|
|
1522
1643
|
throw std::runtime_error(std::strerror(errno));
|
|
@@ -1531,7 +1652,8 @@ class index_gt {
|
|
|
1531
1652
|
|
|
1532
1653
|
// Read the header
|
|
1533
1654
|
{
|
|
1534
|
-
read_chunk(&
|
|
1655
|
+
read_chunk(&state_buffer[0], sizeof(file_header_t));
|
|
1656
|
+
file_head_t state{state_buffer};
|
|
1535
1657
|
if (state.bytes_per_label != sizeof(label_t)) {
|
|
1536
1658
|
std::fclose(file);
|
|
1537
1659
|
throw std::runtime_error("Incompatible label type!");
|
|
@@ -1543,15 +1665,16 @@ class index_gt {
|
|
|
1543
1665
|
|
|
1544
1666
|
config_.connectivity = state.connectivity;
|
|
1545
1667
|
config_.max_elements = state.size;
|
|
1668
|
+
config_.vector_alignment = state.vector_alignment;
|
|
1546
1669
|
pre_ = precompute_(config_);
|
|
1547
1670
|
reserve(state.size);
|
|
1548
1671
|
size_ = state.size;
|
|
1549
1672
|
max_level_ = static_cast<level_t>(state.max_level);
|
|
1550
|
-
entry_id_ = static_cast<id_t>(state.
|
|
1673
|
+
entry_id_ = static_cast<id_t>(state.entry_idx);
|
|
1551
1674
|
}
|
|
1552
1675
|
|
|
1553
1676
|
// Load nodes one by one
|
|
1554
|
-
for (std::size_t i = 0; i !=
|
|
1677
|
+
for (std::size_t i = 0; i != size_; ++i) {
|
|
1555
1678
|
label_t label;
|
|
1556
1679
|
dim_t dim;
|
|
1557
1680
|
level_t level;
|
|
@@ -1578,7 +1701,6 @@ class index_gt {
|
|
|
1578
1701
|
#if defined(USEARCH_IS_WINDOWS)
|
|
1579
1702
|
throw std::logic_error("Memory-mapping is not yet available for Windows");
|
|
1580
1703
|
#else
|
|
1581
|
-
serialized_state_t state;
|
|
1582
1704
|
int open_flags = O_RDONLY;
|
|
1583
1705
|
#if __linux__
|
|
1584
1706
|
open_flags |= O_NOATIME;
|
|
@@ -1602,7 +1724,7 @@ class index_gt {
|
|
|
1602
1724
|
|
|
1603
1725
|
// Read the header
|
|
1604
1726
|
{
|
|
1605
|
-
|
|
1727
|
+
file_head_t state{file};
|
|
1606
1728
|
if (state.bytes_per_label != sizeof(label_t)) {
|
|
1607
1729
|
close(descriptor);
|
|
1608
1730
|
throw std::runtime_error("Incompatible label type!");
|
|
@@ -1614,17 +1736,18 @@ class index_gt {
|
|
|
1614
1736
|
|
|
1615
1737
|
config_.connectivity = state.connectivity;
|
|
1616
1738
|
config_.max_elements = state.size;
|
|
1739
|
+
config_.vector_alignment = state.vector_alignment;
|
|
1617
1740
|
config_.max_threads_add = 0;
|
|
1618
1741
|
pre_ = precompute_(config_);
|
|
1619
1742
|
reserve(state.size);
|
|
1620
1743
|
size_ = state.size;
|
|
1621
1744
|
max_level_ = static_cast<level_t>(state.max_level);
|
|
1622
|
-
entry_id_ = static_cast<id_t>(state.
|
|
1745
|
+
entry_id_ = static_cast<id_t>(state.entry_idx);
|
|
1623
1746
|
}
|
|
1624
1747
|
|
|
1625
1748
|
// Locate every node packed into file
|
|
1626
|
-
std::size_t progress = sizeof(
|
|
1627
|
-
for (std::size_t i = 0; i !=
|
|
1749
|
+
std::size_t progress = sizeof(file_header_t);
|
|
1750
|
+
for (std::size_t i = 0; i != size_; ++i) {
|
|
1628
1751
|
byte_t* tape = (byte_t*)(file + progress);
|
|
1629
1752
|
dim_t dim = misaligned_load<dim_t>(tape + sizeof(label_t));
|
|
1630
1753
|
level_t level = misaligned_load<level_t>(tape + sizeof(label_t) + sizeof(dim_t));
|