usearch 0.18.6 → 0.19.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/Cargo.toml +1 -1
- package/README.md +184 -11
- package/conanfile.py +1 -1
- package/cpp/bench.cpp +2 -2
- package/cpp/test.cpp +11 -9
- package/docs/compilation.md +4 -2
- package/include/usearch/index.hpp +269 -50
- package/include/usearch/index_punned_dense.hpp +123 -108
- package/include/usearch/index_punned_helpers.hpp +5 -13
- package/package.json +1 -1
- package/python/lib.cpp +116 -40
- package/python/scripts/bench.ipynb +4 -4
- package/python/scripts/bench.py +51 -28
- package/python/scripts/index_faiss.py +19 -19
- package/python/scripts/test.py +275 -56
- package/python/usearch/eval.py +100 -49
- package/python/usearch/index.py +118 -88
- package/python/usearch/io.py +23 -23
- package/python/usearch/join.py +2 -4
- package/python/usearch/numba.py +44 -30
- package/python/usearch/server.py +38 -26
- package/simsimd/.vscode/settings.json +2 -1
- package/simsimd/CMakeLists.txt +1 -1
- package/simsimd/README.md +14 -2
- package/simsimd/bench.cxx +44 -27
- package/simsimd/include/simsimd/simsimd.h +22 -7
- package/simsimd/include/simsimd/simsimd.hpp +0 -443
|
@@ -112,12 +112,50 @@ namespace usearch {
|
|
|
112
112
|
using f64_t = double;
|
|
113
113
|
using f32_t = float;
|
|
114
114
|
using byte_t = char;
|
|
115
|
+
enum class b1x8_t : unsigned char {};
|
|
116
|
+
|
|
117
|
+
enum class metric_kind_t : std::uint8_t {
|
|
118
|
+
unknown_k = 0,
|
|
119
|
+
// Classics:
|
|
120
|
+
ip_k = 'i',
|
|
121
|
+
cos_k = 'c',
|
|
122
|
+
l2sq_k = 'e',
|
|
123
|
+
|
|
124
|
+
// Custom:
|
|
125
|
+
pearson_k = 'p',
|
|
126
|
+
haversine_k = 'h',
|
|
127
|
+
|
|
128
|
+
// Sets:
|
|
129
|
+
jaccard_k = 'j',
|
|
130
|
+
hamming_k = 'b',
|
|
131
|
+
tanimoto_k = 't',
|
|
132
|
+
sorensen_k = 's',
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
enum class scalar_kind_t : std::uint8_t {
|
|
136
|
+
unknown_k = 0,
|
|
137
|
+
f64_k,
|
|
138
|
+
f32_k,
|
|
139
|
+
f16_k,
|
|
140
|
+
f8_k,
|
|
141
|
+
b1x8_k,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
template <typename scalar_at> scalar_kind_t common_scalar_kind() noexcept {
|
|
145
|
+
if (std::is_same<scalar_at, f32_t>())
|
|
146
|
+
return scalar_kind_t::f32_k;
|
|
147
|
+
if (std::is_same<scalar_at, f64_t>())
|
|
148
|
+
return scalar_kind_t::f64_k;
|
|
149
|
+
if (std::is_same<scalar_at, b1x8_t>())
|
|
150
|
+
return scalar_kind_t::b1x8_k;
|
|
151
|
+
return scalar_kind_t::unknown_k;
|
|
152
|
+
}
|
|
115
153
|
|
|
116
154
|
template <typename at> at angle_to_radians(at angle) noexcept { return angle * at(3.14159265358979323846) / at(180); }
|
|
117
155
|
|
|
118
156
|
template <typename at> at square(at value) noexcept { return value * value; }
|
|
119
157
|
|
|
120
|
-
template <std::size_t multiple_ak>
|
|
158
|
+
template <std::size_t multiple_ak> std::size_t divide_round_up(std::size_t num) noexcept {
|
|
121
159
|
return (num + multiple_ak - 1) / multiple_ak;
|
|
122
160
|
}
|
|
123
161
|
|
|
@@ -225,6 +263,8 @@ template <typename scalar_at = float, typename result_at = scalar_at> struct ip_
|
|
|
225
263
|
using result_t = result_at;
|
|
226
264
|
using result_type = result_t;
|
|
227
265
|
|
|
266
|
+
inline metric_kind_t kind() const noexcept { return metric_kind_t::ip_k; }
|
|
267
|
+
inline scalar_kind_t scalar_kind() const noexcept { return common_scalar_kind<scalar_t>(); }
|
|
228
268
|
inline result_t operator()(view_t a, view_t b) const noexcept { return operator()(a.data(), b.data(), a.size()); }
|
|
229
269
|
|
|
230
270
|
inline result_t operator()(scalar_t const* a, scalar_t const* b, std::size_t dim) const noexcept {
|
|
@@ -254,6 +294,8 @@ template <typename scalar_at = float, typename result_at = scalar_at> struct cos
|
|
|
254
294
|
using result_t = result_at;
|
|
255
295
|
using result_type = result_t;
|
|
256
296
|
|
|
297
|
+
inline metric_kind_t kind() const noexcept { return metric_kind_t::cos_k; }
|
|
298
|
+
inline scalar_kind_t scalar_kind() const noexcept { return common_scalar_kind<scalar_t>(); }
|
|
257
299
|
inline result_t operator()(view_t a, view_t b) const noexcept { return operator()(a.data(), b.data(), a.size()); }
|
|
258
300
|
|
|
259
301
|
inline result_t operator()(scalar_t const* a, scalar_t const* b, std::size_t dim) const noexcept {
|
|
@@ -283,6 +325,8 @@ template <typename scalar_at = float, typename result_at = scalar_at> struct l2s
|
|
|
283
325
|
using result_t = result_at;
|
|
284
326
|
using result_type = result_t;
|
|
285
327
|
|
|
328
|
+
inline metric_kind_t kind() const noexcept { return metric_kind_t::l2sq_k; }
|
|
329
|
+
inline scalar_kind_t scalar_kind() const noexcept { return common_scalar_kind<scalar_t>(); }
|
|
286
330
|
inline result_t operator()(view_t a, view_t b) const noexcept { return operator()(a.data(), b.data(), a.size()); }
|
|
287
331
|
|
|
288
332
|
inline result_t operator()(scalar_t const* a, scalar_t const* b, std::size_t dim) const noexcept {
|
|
@@ -312,6 +356,8 @@ template <typename scalar_at = std::uint64_t, typename result_at = std::size_t>
|
|
|
312
356
|
using result_type = result_t;
|
|
313
357
|
static_assert(std::is_unsigned<scalar_t>::value, "Hamming distance requires unsigned integral words");
|
|
314
358
|
|
|
359
|
+
inline metric_kind_t kind() const noexcept { return metric_kind_t::hamming_k; }
|
|
360
|
+
inline scalar_kind_t scalar_kind() const noexcept { return common_scalar_kind<scalar_t>(); }
|
|
315
361
|
inline result_t operator()(view_t a, view_t b) const noexcept { return operator()(a.data(), b.data(), a.size()); }
|
|
316
362
|
|
|
317
363
|
inline result_t operator()(scalar_t const* a, scalar_t const* b, std::size_t words) const noexcept {
|
|
@@ -342,6 +388,8 @@ template <typename scalar_at = std::uint64_t, typename result_at = float> struct
|
|
|
342
388
|
static_assert(std::is_unsigned<scalar_t>::value, "Tanimoto distance requires unsigned integral words");
|
|
343
389
|
static_assert(std::is_floating_point<result_t>::value, "Tanimoto distance will be a fraction");
|
|
344
390
|
|
|
391
|
+
inline metric_kind_t kind() const noexcept { return metric_kind_t::tanimoto_k; }
|
|
392
|
+
inline scalar_kind_t scalar_kind() const noexcept { return common_scalar_kind<scalar_t>(); }
|
|
345
393
|
inline result_t operator()(view_t a, view_t b) const noexcept { return operator()(a.data(), b.data(), a.size()); }
|
|
346
394
|
|
|
347
395
|
inline result_t operator()(scalar_t const* a, scalar_t const* b, std::size_t words) const noexcept {
|
|
@@ -374,6 +422,8 @@ template <typename scalar_at = std::uint64_t, typename result_at = float> struct
|
|
|
374
422
|
static_assert(std::is_unsigned<scalar_t>::value, "Sorensen-Dice distance requires unsigned integral words");
|
|
375
423
|
static_assert(std::is_floating_point<result_t>::value, "Sorensen-Dice distance will be a fraction");
|
|
376
424
|
|
|
425
|
+
inline metric_kind_t kind() const noexcept { return metric_kind_t::sorensen_k; }
|
|
426
|
+
inline scalar_kind_t scalar_kind() const noexcept { return common_scalar_kind<scalar_t>(); }
|
|
377
427
|
inline result_t operator()(view_t a, view_t b) const noexcept { return operator()(a.data(), b.data(), a.size()); }
|
|
378
428
|
|
|
379
429
|
inline result_t operator()(scalar_t const* a, scalar_t const* b, std::size_t words) const noexcept {
|
|
@@ -407,6 +457,8 @@ template <typename scalar_at = std::int32_t, typename result_at = float> struct
|
|
|
407
457
|
using result_type = result_t;
|
|
408
458
|
static_assert(!std::is_floating_point<scalar_t>::value, "Jaccard distance requires integral scalars");
|
|
409
459
|
|
|
460
|
+
inline metric_kind_t kind() const noexcept { return metric_kind_t::jaccard_k; }
|
|
461
|
+
inline scalar_kind_t scalar_kind() const noexcept { return common_scalar_kind<scalar_t>(); }
|
|
410
462
|
inline result_t operator()(view_t a, view_t b) const noexcept {
|
|
411
463
|
return operator()(a.data(), b.data(), a.size(), b.size());
|
|
412
464
|
}
|
|
@@ -436,6 +488,8 @@ template <typename scalar_at = float, typename result_at = float> struct pearson
|
|
|
436
488
|
using result_t = result_at;
|
|
437
489
|
using result_type = result_t;
|
|
438
490
|
|
|
491
|
+
inline metric_kind_t kind() const noexcept { return metric_kind_t::pearson_k; }
|
|
492
|
+
inline scalar_kind_t scalar_kind() const noexcept { return common_scalar_kind<scalar_t>(); }
|
|
439
493
|
inline result_t operator()(view_t a, view_t b) const noexcept { return operator()(a.data(), b.data(), a.size()); }
|
|
440
494
|
|
|
441
495
|
inline result_t operator()(scalar_t const* a, scalar_t const* b, std::size_t dim) const noexcept {
|
|
@@ -472,6 +526,9 @@ template <typename scalar_at = float, typename result_at = scalar_at> struct hav
|
|
|
472
526
|
using result_type = result_t;
|
|
473
527
|
static_assert(!std::is_integral<scalar_t>::value, "Latitude and longitude must be floating-node");
|
|
474
528
|
|
|
529
|
+
inline metric_kind_t kind() const noexcept { return metric_kind_t::haversine_k; }
|
|
530
|
+
inline scalar_kind_t scalar_kind() const noexcept { return common_scalar_kind<scalar_t>(); }
|
|
531
|
+
|
|
475
532
|
inline result_t operator()(scalar_t const* a, scalar_t const* b) const noexcept {
|
|
476
533
|
result_t lat_a = a[0], lon_a = a[1];
|
|
477
534
|
result_t lat_b = b[0], lon_b = b[1];
|
|
@@ -964,9 +1021,9 @@ constexpr std::size_t default_allocator_entry_bytes() { return 64; }
|
|
|
964
1021
|
|
|
965
1022
|
/**
|
|
966
1023
|
* @brief The "magic" sequence helps infer the type of the file.
|
|
967
|
-
* USearch indexes start with the "
|
|
1024
|
+
* USearch indexes start with the "usearch" string.
|
|
968
1025
|
*/
|
|
969
|
-
constexpr char const* default_magic() { return "
|
|
1026
|
+
constexpr char const* default_magic() { return "usearch"; }
|
|
970
1027
|
|
|
971
1028
|
/**
|
|
972
1029
|
* @brief Configuration settings for the index construction.
|
|
@@ -1026,59 +1083,56 @@ struct search_config_t {
|
|
|
1026
1083
|
bool exact = false;
|
|
1027
1084
|
};
|
|
1028
1085
|
|
|
1029
|
-
enum class metric_kind_t : std::uint8_t {
|
|
1030
|
-
unknown_k = 0,
|
|
1031
|
-
// Classics:
|
|
1032
|
-
ip_k = 'i',
|
|
1033
|
-
cos_k = 'c',
|
|
1034
|
-
l2sq_k = 'e',
|
|
1035
|
-
|
|
1036
|
-
// Custom:
|
|
1037
|
-
pearson_k = 'p',
|
|
1038
|
-
haversine_k = 'h',
|
|
1039
|
-
|
|
1040
|
-
// Sets:
|
|
1041
|
-
jaccard_k = 'j',
|
|
1042
|
-
hamming_k = 'b',
|
|
1043
|
-
tanimoto_k = 't',
|
|
1044
|
-
sorensen_k = 's',
|
|
1045
|
-
};
|
|
1046
|
-
|
|
1047
1086
|
using file_header_t = byte_t[64];
|
|
1048
1087
|
|
|
1049
1088
|
/**
|
|
1050
1089
|
* @brief Serialized binary representations of the USearch index start with metadata.
|
|
1051
1090
|
* Metadata is parsed into a `file_head_t`, containing the USearch package version,
|
|
1052
1091
|
* and the properties of the index.
|
|
1092
|
+
*
|
|
1093
|
+
* It uses: 13 bytes for file versioning, 22 bytes for structural information = 35 bytes.
|
|
1094
|
+
* The following 24 bytes contain binary size of the graph, of the vectors, and the checksum,
|
|
1095
|
+
* leaving 5 bytes at the end vacant.
|
|
1053
1096
|
*/
|
|
1054
1097
|
struct file_head_t {
|
|
1055
1098
|
|
|
1056
|
-
|
|
1099
|
+
// Versioning: 7 + 2 * 3 = 13 bytes
|
|
1100
|
+
using magic_t = char[7];
|
|
1057
1101
|
using version_major_t = std::uint16_t;
|
|
1058
1102
|
using version_minor_t = std::uint16_t;
|
|
1059
1103
|
using version_patch_t = std::uint16_t;
|
|
1060
1104
|
|
|
1105
|
+
// Structural: 1 * 6 + 8 * 2 = 22 bytes
|
|
1061
1106
|
using connectivity_t = std::uint8_t;
|
|
1062
1107
|
using max_level_t = std::uint8_t;
|
|
1063
1108
|
using vector_alignment_t = std::uint8_t;
|
|
1064
|
-
using bytes_per_label_t = std::
|
|
1065
|
-
using bytes_per_id_t = std::
|
|
1109
|
+
using bytes_per_label_t = std::uint8_t;
|
|
1110
|
+
using bytes_per_id_t = std::uint8_t;
|
|
1066
1111
|
using size_t = std::uint64_t;
|
|
1067
1112
|
using entry_idx_t = std::uint64_t;
|
|
1068
1113
|
|
|
1114
|
+
// Versioning:
|
|
1069
1115
|
char const* magic;
|
|
1070
1116
|
misaligned_ref_gt<version_major_t> version_major;
|
|
1071
1117
|
misaligned_ref_gt<version_minor_t> version_minor;
|
|
1072
1118
|
misaligned_ref_gt<version_patch_t> version_patch;
|
|
1119
|
+
|
|
1120
|
+
// Structural:
|
|
1073
1121
|
misaligned_ref_gt<metric_kind_t> metric;
|
|
1074
1122
|
misaligned_ref_gt<connectivity_t> connectivity;
|
|
1075
1123
|
misaligned_ref_gt<max_level_t> max_level;
|
|
1076
1124
|
misaligned_ref_gt<vector_alignment_t> vector_alignment;
|
|
1077
1125
|
misaligned_ref_gt<bytes_per_label_t> bytes_per_label;
|
|
1078
1126
|
misaligned_ref_gt<bytes_per_id_t> bytes_per_id;
|
|
1127
|
+
misaligned_ref_gt<scalar_kind_t> scalar_kind;
|
|
1079
1128
|
misaligned_ref_gt<size_t> size;
|
|
1080
1129
|
misaligned_ref_gt<entry_idx_t> entry_idx;
|
|
1081
1130
|
|
|
1131
|
+
// Additional:
|
|
1132
|
+
misaligned_ref_gt<size_t> bytes_for_graphs;
|
|
1133
|
+
misaligned_ref_gt<size_t> bytes_for_vectors;
|
|
1134
|
+
misaligned_ref_gt<size_t> bytes_checksum;
|
|
1135
|
+
|
|
1082
1136
|
file_head_t(byte_t* ptr) noexcept
|
|
1083
1137
|
: magic((char const*)exchange(ptr, ptr + sizeof(magic_t))),
|
|
1084
1138
|
version_major(exchange(ptr, ptr + sizeof(version_major_t))),
|
|
@@ -1088,8 +1142,55 @@ struct file_head_t {
|
|
|
1088
1142
|
max_level(exchange(ptr, ptr + sizeof(max_level_t))),
|
|
1089
1143
|
vector_alignment(exchange(ptr, ptr + sizeof(vector_alignment_t))),
|
|
1090
1144
|
bytes_per_label(exchange(ptr, ptr + sizeof(bytes_per_label_t))),
|
|
1091
|
-
bytes_per_id(exchange(ptr, ptr + sizeof(bytes_per_id_t))),
|
|
1092
|
-
|
|
1145
|
+
bytes_per_id(exchange(ptr, ptr + sizeof(bytes_per_id_t))),
|
|
1146
|
+
scalar_kind(exchange(ptr, ptr + sizeof(scalar_kind_t))), size(exchange(ptr, ptr + sizeof(size_t))),
|
|
1147
|
+
entry_idx(exchange(ptr, ptr + sizeof(entry_idx_t))), bytes_for_graphs(exchange(ptr, ptr + sizeof(size_t))),
|
|
1148
|
+
bytes_for_vectors(exchange(ptr, ptr + sizeof(size_t))), bytes_checksum(exchange(ptr, ptr + sizeof(size_t))) {}
|
|
1149
|
+
};
|
|
1150
|
+
|
|
1151
|
+
struct file_head_result_t {
|
|
1152
|
+
|
|
1153
|
+
using magic_t = file_head_t::magic_t;
|
|
1154
|
+
using version_major_t = file_head_t::version_major_t;
|
|
1155
|
+
using version_minor_t = file_head_t::version_minor_t;
|
|
1156
|
+
using version_patch_t = file_head_t::version_patch_t;
|
|
1157
|
+
|
|
1158
|
+
using connectivity_t = file_head_t::connectivity_t;
|
|
1159
|
+
using max_level_t = file_head_t::max_level_t;
|
|
1160
|
+
using vector_alignment_t = file_head_t::vector_alignment_t;
|
|
1161
|
+
using bytes_per_label_t = file_head_t::bytes_per_label_t;
|
|
1162
|
+
using bytes_per_id_t = file_head_t::bytes_per_id_t;
|
|
1163
|
+
using size_t = file_head_t::size_t;
|
|
1164
|
+
using entry_idx_t = file_head_t::entry_idx_t;
|
|
1165
|
+
|
|
1166
|
+
// Versioning:
|
|
1167
|
+
version_major_t version_major;
|
|
1168
|
+
version_minor_t version_minor;
|
|
1169
|
+
version_patch_t version_patch;
|
|
1170
|
+
|
|
1171
|
+
// Structural:
|
|
1172
|
+
metric_kind_t metric;
|
|
1173
|
+
connectivity_t connectivity;
|
|
1174
|
+
max_level_t max_level;
|
|
1175
|
+
vector_alignment_t vector_alignment;
|
|
1176
|
+
bytes_per_label_t bytes_per_label;
|
|
1177
|
+
bytes_per_id_t bytes_per_id;
|
|
1178
|
+
scalar_kind_t scalar_kind;
|
|
1179
|
+
size_t size;
|
|
1180
|
+
entry_idx_t entry_idx;
|
|
1181
|
+
|
|
1182
|
+
// Additional:
|
|
1183
|
+
size_t bytes_for_graphs;
|
|
1184
|
+
size_t bytes_for_vectors;
|
|
1185
|
+
size_t bytes_checksum;
|
|
1186
|
+
|
|
1187
|
+
error_t error;
|
|
1188
|
+
|
|
1189
|
+
explicit operator bool() const noexcept { return !error; }
|
|
1190
|
+
file_head_result_t failed(char const* message) noexcept {
|
|
1191
|
+
error = message;
|
|
1192
|
+
return std::move(*this);
|
|
1193
|
+
}
|
|
1093
1194
|
};
|
|
1094
1195
|
|
|
1095
1196
|
static_assert(sizeof(file_header_t) == 64, "File header should be exactly 64 bytes");
|
|
@@ -1103,6 +1204,24 @@ using return_type_gt =
|
|
|
1103
1204
|
typename std::result_of<metric_at(args_at...)>::type;
|
|
1104
1205
|
#endif
|
|
1105
1206
|
|
|
1207
|
+
/// @brief OS-specific to wrap open file-descriptors/handles.
|
|
1208
|
+
#if defined(USEARCH_DEFINED_WINDOWS)
|
|
1209
|
+
struct viewed_file_t {
|
|
1210
|
+
HANDLE file_handle{};
|
|
1211
|
+
HANDLE mapping_handle{};
|
|
1212
|
+
void* ptr{};
|
|
1213
|
+
size_t length{};
|
|
1214
|
+
explicit operator bool() const noexcept { return mapping_handle != nullptr; }
|
|
1215
|
+
};
|
|
1216
|
+
#else
|
|
1217
|
+
struct viewed_file_t {
|
|
1218
|
+
int file_descriptor{};
|
|
1219
|
+
void* ptr{};
|
|
1220
|
+
size_t length{};
|
|
1221
|
+
explicit operator bool() const noexcept { return file_descriptor != 0; }
|
|
1222
|
+
};
|
|
1223
|
+
#endif
|
|
1224
|
+
|
|
1106
1225
|
/**
|
|
1107
1226
|
* @brief Approximate Nearest Neighbors Search index using the
|
|
1108
1227
|
* Hierarchical Navigable Small World graph algorithm.
|
|
@@ -1168,18 +1287,17 @@ using return_type_gt =
|
|
|
1168
1287
|
template <typename metric_at = ip_gt<float>, //
|
|
1169
1288
|
typename label_at = std::size_t, //
|
|
1170
1289
|
typename id_at = std::uint32_t, //
|
|
1171
|
-
typename scalar_at = float, //
|
|
1172
1290
|
typename allocator_at = std::allocator<char>, //
|
|
1173
1291
|
typename point_allocator_at = allocator_at> //
|
|
1174
1292
|
class index_gt {
|
|
1175
1293
|
public:
|
|
1176
1294
|
using metric_t = metric_at;
|
|
1177
|
-
using scalar_t = scalar_at;
|
|
1178
1295
|
using label_t = label_at;
|
|
1179
1296
|
using id_t = id_at;
|
|
1180
1297
|
using allocator_t = allocator_at;
|
|
1181
1298
|
using point_allocator_t = point_allocator_at;
|
|
1182
1299
|
|
|
1300
|
+
using scalar_t = typename metric_t::scalar_t;
|
|
1183
1301
|
using vector_view_t = span_gt<scalar_t const>;
|
|
1184
1302
|
using distance_t = return_type_gt<metric_t, vector_view_t, vector_view_t>;
|
|
1185
1303
|
|
|
@@ -1390,7 +1508,7 @@ class index_gt {
|
|
|
1390
1508
|
allocator_t allocator_{};
|
|
1391
1509
|
point_allocator_t point_allocator_{};
|
|
1392
1510
|
precomputed_constants_t pre_{};
|
|
1393
|
-
|
|
1511
|
+
viewed_file_t viewed_file_{};
|
|
1394
1512
|
|
|
1395
1513
|
usearch_align_m mutable std::atomic<std::size_t> capacity_{};
|
|
1396
1514
|
usearch_align_m mutable std::atomic<std::size_t> size_{};
|
|
@@ -1415,7 +1533,7 @@ class index_gt {
|
|
|
1415
1533
|
std::size_t max_level() const noexcept { return static_cast<std::size_t>(max_level_); }
|
|
1416
1534
|
index_config_t const& config() const noexcept { return config_; }
|
|
1417
1535
|
index_limits_t const& limits() const noexcept { return limits_; }
|
|
1418
|
-
bool is_immutable() const noexcept { return
|
|
1536
|
+
bool is_immutable() const noexcept { return bool(viewed_file_); }
|
|
1419
1537
|
|
|
1420
1538
|
/**
|
|
1421
1539
|
* @section Exceptions
|
|
@@ -1424,8 +1542,8 @@ class index_gt {
|
|
|
1424
1542
|
explicit index_gt(index_config_t config = {}, metric_t metric = {}, allocator_t allocator = {},
|
|
1425
1543
|
point_allocator_t point_allocator = {}) noexcept
|
|
1426
1544
|
: config_(config), limits_(0, 0), metric_(metric), allocator_(std::move(allocator)),
|
|
1427
|
-
point_allocator_(std::move(point_allocator)), pre_(precompute_(config)),
|
|
1428
|
-
|
|
1545
|
+
point_allocator_(std::move(point_allocator)), pre_(precompute_(config)), size_(0u), max_level_(-1),
|
|
1546
|
+
entry_id_(0u), nodes_(nullptr), nodes_mutexes_(), contexts_(nullptr) {}
|
|
1429
1547
|
|
|
1430
1548
|
/**
|
|
1431
1549
|
* @brief Clones the structure with the same hyper-parameters, but without contents.
|
|
@@ -1476,6 +1594,7 @@ class index_gt {
|
|
|
1476
1594
|
context_allocator_t{}.deallocate(exchange(contexts_, nullptr), limits_.threads());
|
|
1477
1595
|
limits_ = index_limits_t{0, 0};
|
|
1478
1596
|
capacity_ = 0;
|
|
1597
|
+
reset_view_();
|
|
1479
1598
|
}
|
|
1480
1599
|
|
|
1481
1600
|
/**
|
|
@@ -1487,7 +1606,7 @@ class index_gt {
|
|
|
1487
1606
|
std::swap(metric_, other.metric_);
|
|
1488
1607
|
std::swap(allocator_, other.allocator_);
|
|
1489
1608
|
std::swap(pre_, other.pre_);
|
|
1490
|
-
std::swap(
|
|
1609
|
+
std::swap(viewed_file_, other.viewed_file_);
|
|
1491
1610
|
std::swap(max_level_, other.max_level_);
|
|
1492
1611
|
std::swap(entry_id_, other.entry_id_);
|
|
1493
1612
|
std::swap(nodes_, other.nodes_);
|
|
@@ -1854,7 +1973,7 @@ class index_gt {
|
|
|
1854
1973
|
*/
|
|
1855
1974
|
std::size_t memory_usage(std::size_t allocator_entry_bytes = default_allocator_entry_bytes()) const noexcept {
|
|
1856
1975
|
std::size_t total = 0;
|
|
1857
|
-
if (
|
|
1976
|
+
if (!viewed_file_) {
|
|
1858
1977
|
stats_t s = stats();
|
|
1859
1978
|
total += s.allocated_bytes;
|
|
1860
1979
|
total += s.nodes * allocator_entry_bytes;
|
|
@@ -1895,17 +2014,23 @@ class index_gt {
|
|
|
1895
2014
|
*/
|
|
1896
2015
|
serialization_result_t save(char const* file_path) const noexcept {
|
|
1897
2016
|
|
|
2017
|
+
// Make sure we have right to write to that file
|
|
1898
2018
|
serialization_result_t result;
|
|
2019
|
+
std::FILE* file = std::fopen(file_path, "wb");
|
|
2020
|
+
if (!file)
|
|
2021
|
+
return result.failed(std::strerror(errno));
|
|
2022
|
+
|
|
2023
|
+
// Prepare the header with metadata
|
|
1899
2024
|
file_header_t state_buffer{};
|
|
1900
2025
|
std::memset(state_buffer, 0, sizeof(file_header_t));
|
|
1901
2026
|
file_head_t state{state_buffer};
|
|
1902
2027
|
std::memcpy(state_buffer, default_magic(), std::strlen(default_magic()));
|
|
1903
2028
|
|
|
1904
|
-
//
|
|
2029
|
+
// Mark compatibility
|
|
1905
2030
|
state.version_major = USEARCH_VERSION_MAJOR;
|
|
1906
2031
|
state.version_minor = USEARCH_VERSION_MINOR;
|
|
1907
2032
|
state.version_patch = USEARCH_VERSION_PATCH;
|
|
1908
|
-
state.metric =
|
|
2033
|
+
state.metric = metric_.kind();
|
|
1909
2034
|
|
|
1910
2035
|
// Describe state
|
|
1911
2036
|
state.connectivity = config_.connectivity;
|
|
@@ -1913,13 +2038,25 @@ class index_gt {
|
|
|
1913
2038
|
state.vector_alignment = config_.vector_alignment;
|
|
1914
2039
|
state.bytes_per_label = sizeof(label_t);
|
|
1915
2040
|
state.bytes_per_id = sizeof(id_t);
|
|
2041
|
+
state.scalar_kind = metric_.scalar_kind();
|
|
1916
2042
|
state.size = size_;
|
|
1917
2043
|
state.entry_idx = entry_id_;
|
|
1918
2044
|
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
2045
|
+
// Augment with metadata
|
|
2046
|
+
std::size_t graphs_bytes = 0;
|
|
2047
|
+
std::size_t vectors_bytes = 0;
|
|
2048
|
+
for (std::size_t i = 0; i != state.size; ++i) {
|
|
2049
|
+
node_t node = node_with_id_(i);
|
|
2050
|
+
std::size_t node_bytes = node_bytes_(node);
|
|
2051
|
+
std::size_t node_vector_bytes = node_vector_bytes_(node);
|
|
2052
|
+
graphs_bytes += node_bytes - node_vector_bytes;
|
|
2053
|
+
vectors_bytes += node_vector_bytes;
|
|
2054
|
+
}
|
|
2055
|
+
state.bytes_for_graphs = graphs_bytes;
|
|
2056
|
+
state.bytes_for_vectors = vectors_bytes;
|
|
2057
|
+
state.bytes_checksum = 0;
|
|
1922
2058
|
|
|
2059
|
+
// Perform serialization
|
|
1923
2060
|
auto write_chunk = [&](void* begin, std::size_t length) {
|
|
1924
2061
|
std::size_t written = std::fwrite(begin, length, 1, file);
|
|
1925
2062
|
if (!written) {
|
|
@@ -2027,7 +2164,7 @@ class index_gt {
|
|
|
2027
2164
|
}
|
|
2028
2165
|
|
|
2029
2166
|
std::fclose(file);
|
|
2030
|
-
|
|
2167
|
+
reset_view_();
|
|
2031
2168
|
return {};
|
|
2032
2169
|
}
|
|
2033
2170
|
|
|
@@ -2038,14 +2175,38 @@ class index_gt {
|
|
|
2038
2175
|
*/
|
|
2039
2176
|
serialization_result_t view(char const* file_path) noexcept {
|
|
2040
2177
|
serialization_result_t result;
|
|
2178
|
+
|
|
2041
2179
|
#if defined(USEARCH_DEFINED_WINDOWS)
|
|
2042
|
-
|
|
2180
|
+
|
|
2181
|
+
HANDLE file_handle =
|
|
2182
|
+
CreateFile(file_path, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
|
|
2183
|
+
if (file_handle == INVALID_HANDLE_VALUE)
|
|
2184
|
+
return result.failed("Opening file failed!");
|
|
2185
|
+
|
|
2186
|
+
size_t file_length = GetFileSize(file_handle, 0);
|
|
2187
|
+
HANDLE mapping_handle = CreateFileMapping(file_handle, 0, PAGE_READONLY, 0, 0, 0);
|
|
2188
|
+
if (mapping_handle == 0) {
|
|
2189
|
+
CloseHandle(file_handle);
|
|
2190
|
+
return result.failed("Mapping file failed!");
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
byte_t* file = (byte_t*)MapViewOfFile(mapping_handle, FILE_MAP_READ, 0, 0, file_length);
|
|
2194
|
+
if (file == 0) {
|
|
2195
|
+
CloseHandle(mapping_handle);
|
|
2196
|
+
CloseHandle(file_handle);
|
|
2197
|
+
return result.failed("View the map failed!");
|
|
2198
|
+
}
|
|
2199
|
+
viewed_file_.file_handle = file_handle;
|
|
2200
|
+
viewed_file_.mapping_handle = mapping_handle;
|
|
2201
|
+
viewed_file_.ptr = file;
|
|
2202
|
+
viewed_file_.length = file_length;
|
|
2203
|
+
#else
|
|
2204
|
+
|
|
2205
|
+
#if defined(USEARCH_DEFINED_LINUX)
|
|
2206
|
+
int descriptor = open(file_path, O_RDONLY | O_NOATIME);
|
|
2043
2207
|
#else
|
|
2044
|
-
int
|
|
2045
|
-
#if __linux__
|
|
2046
|
-
open_flags |= O_NOATIME;
|
|
2208
|
+
int descriptor = open(file_path, O_RDONLY);
|
|
2047
2209
|
#endif
|
|
2048
|
-
int descriptor = open(file_path, open_flags);
|
|
2049
2210
|
|
|
2050
2211
|
// Estimate the file size
|
|
2051
2212
|
struct stat file_stat;
|
|
@@ -2061,16 +2222,20 @@ class index_gt {
|
|
|
2061
2222
|
close(descriptor);
|
|
2062
2223
|
return result.failed(std::strerror(errno));
|
|
2063
2224
|
}
|
|
2225
|
+
viewed_file_.file_descriptor = descriptor;
|
|
2226
|
+
viewed_file_.ptr = file;
|
|
2227
|
+
viewed_file_.length = file_stat.st_size;
|
|
2228
|
+
#endif // Platform specific code
|
|
2064
2229
|
|
|
2065
2230
|
// Read the header
|
|
2066
2231
|
{
|
|
2067
2232
|
file_head_t state{file};
|
|
2068
2233
|
if (state.bytes_per_label != sizeof(label_t)) {
|
|
2069
|
-
|
|
2234
|
+
reset_view_();
|
|
2070
2235
|
return result.failed("Incompatible label type!");
|
|
2071
2236
|
}
|
|
2072
2237
|
if (state.bytes_per_id != sizeof(id_t)) {
|
|
2073
|
-
|
|
2238
|
+
reset_view_();
|
|
2074
2239
|
return result.failed("Incompatible ID type!");
|
|
2075
2240
|
}
|
|
2076
2241
|
|
|
@@ -2103,14 +2268,27 @@ class index_gt {
|
|
|
2103
2268
|
max_level_ = (std::max)(max_level_, level);
|
|
2104
2269
|
}
|
|
2105
2270
|
|
|
2106
|
-
viewed_file_descriptor_ = descriptor;
|
|
2107
2271
|
return {};
|
|
2108
|
-
#endif // POSIX
|
|
2109
2272
|
}
|
|
2110
2273
|
|
|
2111
2274
|
#pragma endregion
|
|
2112
2275
|
|
|
2113
2276
|
private:
|
|
2277
|
+
void reset_view_() noexcept {
|
|
2278
|
+
if (!viewed_file_)
|
|
2279
|
+
return;
|
|
2280
|
+
#if defined(USEARCH_DEFINED_WINDOWS)
|
|
2281
|
+
UnmapViewOfFile(viewed_file_.ptr);
|
|
2282
|
+
CloseHandle(viewed_file_.mapping_handle);
|
|
2283
|
+
CloseHandle(viewed_file_.file_handle);
|
|
2284
|
+
|
|
2285
|
+
#else
|
|
2286
|
+
munmap(viewed_file_.ptr, viewed_file_.length);
|
|
2287
|
+
close(viewed_file_.file_descriptor);
|
|
2288
|
+
#endif
|
|
2289
|
+
viewed_file_ = {};
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2114
2292
|
inline static precomputed_constants_t precompute_(index_config_t const& config) noexcept {
|
|
2115
2293
|
precomputed_constants_t pre;
|
|
2116
2294
|
pre.connectivity_max_base = config.connectivity * base_level_multiple_();
|
|
@@ -2135,7 +2313,7 @@ class index_gt {
|
|
|
2135
2313
|
|
|
2136
2314
|
void node_free_(std::size_t id) noexcept {
|
|
2137
2315
|
|
|
2138
|
-
if (
|
|
2316
|
+
if (viewed_file_)
|
|
2139
2317
|
return;
|
|
2140
2318
|
|
|
2141
2319
|
node_t& node = nodes_[id];
|
|
@@ -2444,6 +2622,47 @@ class index_gt {
|
|
|
2444
2622
|
}
|
|
2445
2623
|
};
|
|
2446
2624
|
|
|
2625
|
+
/**
|
|
2626
|
+
* @brief Extracts metadata from pre-constructed index on disk, without loading it
|
|
2627
|
+
* or mapping the whole binary file.
|
|
2628
|
+
*/
|
|
2629
|
+
inline file_head_result_t index_metadata(char const* file_path) noexcept {
|
|
2630
|
+
|
|
2631
|
+
file_head_result_t result;
|
|
2632
|
+
file_header_t state_buffer{};
|
|
2633
|
+
std::FILE* file = std::fopen(file_path, "rb");
|
|
2634
|
+
if (!file)
|
|
2635
|
+
return result.failed(std::strerror(errno));
|
|
2636
|
+
|
|
2637
|
+
// Read the header
|
|
2638
|
+
std::size_t read = std::fread(&state_buffer[0], sizeof(file_header_t), 1, file);
|
|
2639
|
+
std::fclose(file);
|
|
2640
|
+
if (!read)
|
|
2641
|
+
return result.failed(std::strerror(errno));
|
|
2642
|
+
|
|
2643
|
+
// Parse and validate the MIME type
|
|
2644
|
+
file_head_t state{state_buffer};
|
|
2645
|
+
if (std::strncmp(state.magic, default_magic(), std::strlen(default_magic())) != 0)
|
|
2646
|
+
return result.failed("Wrong MIME type!");
|
|
2647
|
+
|
|
2648
|
+
result.version_major = state.version_major;
|
|
2649
|
+
result.version_minor = state.version_minor;
|
|
2650
|
+
result.version_patch = state.version_patch;
|
|
2651
|
+
result.metric = state.metric;
|
|
2652
|
+
result.connectivity = state.connectivity;
|
|
2653
|
+
result.max_level = state.max_level;
|
|
2654
|
+
result.vector_alignment = state.vector_alignment;
|
|
2655
|
+
result.bytes_per_label = state.bytes_per_label;
|
|
2656
|
+
result.bytes_per_id = state.bytes_per_id;
|
|
2657
|
+
result.scalar_kind = state.scalar_kind;
|
|
2658
|
+
result.size = state.size;
|
|
2659
|
+
result.entry_idx = state.entry_idx;
|
|
2660
|
+
result.bytes_for_graphs = state.bytes_for_graphs;
|
|
2661
|
+
result.bytes_for_vectors = state.bytes_for_vectors;
|
|
2662
|
+
result.bytes_checksum = state.bytes_checksum;
|
|
2663
|
+
return result;
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2447
2666
|
} // namespace usearch
|
|
2448
2667
|
} // namespace unum
|
|
2449
2668
|
|