usearch 0.18.8 → 0.19.1
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 +187 -40
- package/c/lib.cpp +71 -51
- package/c/test.c +182 -0
- package/c/usearch.h +11 -5
- package/conanfile.py +1 -1
- package/cpp/bench.cpp +6 -6
- package/cpp/test.cpp +11 -9
- package/docs/compilation.md +1 -0
- package/include/usearch/index.hpp +290 -54
- package/include/usearch/index_punned_dense.hpp +125 -108
- package/include/usearch/index_punned_helpers.hpp +2 -12
- package/objc/USearchObjective.mm +4 -0
- package/objc/include/USearchObjective.h +5 -0
- package/package.json +2 -2
- package/python/lib.cpp +234 -199
- package/python/scripts/bench.ipynb +13 -2113
- package/python/scripts/bench.py +45 -29
- package/python/scripts/index_faiss.py +19 -19
- package/python/scripts/test.py +320 -61
- package/python/usearch/eval.py +100 -49
- package/python/usearch/index.py +245 -129
- package/python/usearch/io.py +25 -25
- package/python/usearch/join.py +2 -4
- package/python/usearch/numba.py +22 -18
- package/python/usearch/server.py +38 -26
- package/setup.py +56 -59
- 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 +2 -1
- package/swift/Test.swift +13 -9
- 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 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 {
|
|
@@ -269,7 +311,7 @@ template <typename scalar_at = float, typename result_at = scalar_at> struct cos
|
|
|
269
311
|
ab += result_t(a[i]) * result_t(b[i]), //
|
|
270
312
|
a2 += square<result_t>(a[i]), //
|
|
271
313
|
b2 += square<result_t>(b[i]);
|
|
272
|
-
return (ab != 0) ? (1 - ab / (std::sqrt(a2) * std::sqrt(b2))) :
|
|
314
|
+
return (ab != 0) ? (1 - ab / (std::sqrt(a2) * std::sqrt(b2))) : 1;
|
|
273
315
|
}
|
|
274
316
|
};
|
|
275
317
|
|
|
@@ -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 {
|
|
@@ -310,8 +354,13 @@ template <typename scalar_at = std::uint64_t, typename result_at = std::size_t>
|
|
|
310
354
|
using view_t = span_gt<scalar_t const>;
|
|
311
355
|
using result_t = result_at;
|
|
312
356
|
using result_type = result_t;
|
|
313
|
-
static_assert(
|
|
357
|
+
static_assert( //
|
|
358
|
+
std::is_unsigned<scalar_t>::value ||
|
|
359
|
+
(std::is_enum<scalar_t>::value && std::is_unsigned<typename std::underlying_type<scalar_t>::type>::value),
|
|
360
|
+
"Hamming distance requires unsigned integral words");
|
|
314
361
|
|
|
362
|
+
inline metric_kind_t kind() const noexcept { return metric_kind_t::hamming_k; }
|
|
363
|
+
inline scalar_kind_t scalar_kind() const noexcept { return common_scalar_kind<scalar_t>(); }
|
|
315
364
|
inline result_t operator()(view_t a, view_t b) const noexcept { return operator()(a.data(), b.data(), a.size()); }
|
|
316
365
|
|
|
317
366
|
inline result_t operator()(scalar_t const* a, scalar_t const* b, std::size_t words) const noexcept {
|
|
@@ -339,9 +388,14 @@ template <typename scalar_at = std::uint64_t, typename result_at = float> struct
|
|
|
339
388
|
using view_t = span_gt<scalar_t const>;
|
|
340
389
|
using result_t = result_at;
|
|
341
390
|
using result_type = result_t;
|
|
342
|
-
static_assert(
|
|
391
|
+
static_assert( //
|
|
392
|
+
std::is_unsigned<scalar_t>::value ||
|
|
393
|
+
(std::is_enum<scalar_t>::value && std::is_unsigned<typename std::underlying_type<scalar_t>::type>::value),
|
|
394
|
+
"Tanimoto distance requires unsigned integral words");
|
|
343
395
|
static_assert(std::is_floating_point<result_t>::value, "Tanimoto distance will be a fraction");
|
|
344
396
|
|
|
397
|
+
inline metric_kind_t kind() const noexcept { return metric_kind_t::tanimoto_k; }
|
|
398
|
+
inline scalar_kind_t scalar_kind() const noexcept { return common_scalar_kind<scalar_t>(); }
|
|
345
399
|
inline result_t operator()(view_t a, view_t b) const noexcept { return operator()(a.data(), b.data(), a.size()); }
|
|
346
400
|
|
|
347
401
|
inline result_t operator()(scalar_t const* a, scalar_t const* b, std::size_t words) const noexcept {
|
|
@@ -371,9 +425,14 @@ template <typename scalar_at = std::uint64_t, typename result_at = float> struct
|
|
|
371
425
|
using view_t = span_gt<scalar_t const>;
|
|
372
426
|
using result_t = result_at;
|
|
373
427
|
using result_type = result_t;
|
|
374
|
-
static_assert(
|
|
428
|
+
static_assert( //
|
|
429
|
+
std::is_unsigned<scalar_t>::value ||
|
|
430
|
+
(std::is_enum<scalar_t>::value && std::is_unsigned<typename std::underlying_type<scalar_t>::type>::value),
|
|
431
|
+
"Sorensen-Dice distance requires unsigned integral words");
|
|
375
432
|
static_assert(std::is_floating_point<result_t>::value, "Sorensen-Dice distance will be a fraction");
|
|
376
433
|
|
|
434
|
+
inline metric_kind_t kind() const noexcept { return metric_kind_t::sorensen_k; }
|
|
435
|
+
inline scalar_kind_t scalar_kind() const noexcept { return common_scalar_kind<scalar_t>(); }
|
|
377
436
|
inline result_t operator()(view_t a, view_t b) const noexcept { return operator()(a.data(), b.data(), a.size()); }
|
|
378
437
|
|
|
379
438
|
inline result_t operator()(scalar_t const* a, scalar_t const* b, std::size_t words) const noexcept {
|
|
@@ -407,6 +466,8 @@ template <typename scalar_at = std::int32_t, typename result_at = float> struct
|
|
|
407
466
|
using result_type = result_t;
|
|
408
467
|
static_assert(!std::is_floating_point<scalar_t>::value, "Jaccard distance requires integral scalars");
|
|
409
468
|
|
|
469
|
+
inline metric_kind_t kind() const noexcept { return metric_kind_t::jaccard_k; }
|
|
470
|
+
inline scalar_kind_t scalar_kind() const noexcept { return common_scalar_kind<scalar_t>(); }
|
|
410
471
|
inline result_t operator()(view_t a, view_t b) const noexcept {
|
|
411
472
|
return operator()(a.data(), b.data(), a.size(), b.size());
|
|
412
473
|
}
|
|
@@ -436,6 +497,8 @@ template <typename scalar_at = float, typename result_at = float> struct pearson
|
|
|
436
497
|
using result_t = result_at;
|
|
437
498
|
using result_type = result_t;
|
|
438
499
|
|
|
500
|
+
inline metric_kind_t kind() const noexcept { return metric_kind_t::pearson_k; }
|
|
501
|
+
inline scalar_kind_t scalar_kind() const noexcept { return common_scalar_kind<scalar_t>(); }
|
|
439
502
|
inline result_t operator()(view_t a, view_t b) const noexcept { return operator()(a.data(), b.data(), a.size()); }
|
|
440
503
|
|
|
441
504
|
inline result_t operator()(scalar_t const* a, scalar_t const* b, std::size_t dim) const noexcept {
|
|
@@ -472,6 +535,9 @@ template <typename scalar_at = float, typename result_at = scalar_at> struct hav
|
|
|
472
535
|
using result_type = result_t;
|
|
473
536
|
static_assert(!std::is_integral<scalar_t>::value, "Latitude and longitude must be floating-node");
|
|
474
537
|
|
|
538
|
+
inline metric_kind_t kind() const noexcept { return metric_kind_t::haversine_k; }
|
|
539
|
+
inline scalar_kind_t scalar_kind() const noexcept { return common_scalar_kind<scalar_t>(); }
|
|
540
|
+
|
|
475
541
|
inline result_t operator()(scalar_t const* a, scalar_t const* b) const noexcept {
|
|
476
542
|
result_t lat_a = a[0], lon_a = a[1];
|
|
477
543
|
result_t lat_b = b[0], lon_b = b[1];
|
|
@@ -964,9 +1030,9 @@ constexpr std::size_t default_allocator_entry_bytes() { return 64; }
|
|
|
964
1030
|
|
|
965
1031
|
/**
|
|
966
1032
|
* @brief The "magic" sequence helps infer the type of the file.
|
|
967
|
-
* USearch indexes start with the "
|
|
1033
|
+
* USearch indexes start with the "usearch" string.
|
|
968
1034
|
*/
|
|
969
|
-
constexpr char const* default_magic() { return "
|
|
1035
|
+
constexpr char const* default_magic() { return "usearch"; }
|
|
970
1036
|
|
|
971
1037
|
/**
|
|
972
1038
|
* @brief Configuration settings for the index construction.
|
|
@@ -1026,59 +1092,56 @@ struct search_config_t {
|
|
|
1026
1092
|
bool exact = false;
|
|
1027
1093
|
};
|
|
1028
1094
|
|
|
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
1095
|
using file_header_t = byte_t[64];
|
|
1048
1096
|
|
|
1049
1097
|
/**
|
|
1050
1098
|
* @brief Serialized binary representations of the USearch index start with metadata.
|
|
1051
1099
|
* Metadata is parsed into a `file_head_t`, containing the USearch package version,
|
|
1052
1100
|
* and the properties of the index.
|
|
1101
|
+
*
|
|
1102
|
+
* It uses: 13 bytes for file versioning, 22 bytes for structural information = 35 bytes.
|
|
1103
|
+
* The following 24 bytes contain binary size of the graph, of the vectors, and the checksum,
|
|
1104
|
+
* leaving 5 bytes at the end vacant.
|
|
1053
1105
|
*/
|
|
1054
1106
|
struct file_head_t {
|
|
1055
1107
|
|
|
1056
|
-
|
|
1108
|
+
// Versioning: 7 + 2 * 3 = 13 bytes
|
|
1109
|
+
using magic_t = char[7];
|
|
1057
1110
|
using version_major_t = std::uint16_t;
|
|
1058
1111
|
using version_minor_t = std::uint16_t;
|
|
1059
1112
|
using version_patch_t = std::uint16_t;
|
|
1060
1113
|
|
|
1114
|
+
// Structural: 1 * 6 + 8 * 2 = 22 bytes
|
|
1061
1115
|
using connectivity_t = std::uint8_t;
|
|
1062
1116
|
using max_level_t = std::uint8_t;
|
|
1063
1117
|
using vector_alignment_t = std::uint8_t;
|
|
1064
|
-
using bytes_per_label_t = std::
|
|
1065
|
-
using bytes_per_id_t = std::
|
|
1118
|
+
using bytes_per_label_t = std::uint8_t;
|
|
1119
|
+
using bytes_per_id_t = std::uint8_t;
|
|
1066
1120
|
using size_t = std::uint64_t;
|
|
1067
1121
|
using entry_idx_t = std::uint64_t;
|
|
1068
1122
|
|
|
1123
|
+
// Versioning:
|
|
1069
1124
|
char const* magic;
|
|
1070
1125
|
misaligned_ref_gt<version_major_t> version_major;
|
|
1071
1126
|
misaligned_ref_gt<version_minor_t> version_minor;
|
|
1072
1127
|
misaligned_ref_gt<version_patch_t> version_patch;
|
|
1128
|
+
|
|
1129
|
+
// Structural:
|
|
1073
1130
|
misaligned_ref_gt<metric_kind_t> metric;
|
|
1074
1131
|
misaligned_ref_gt<connectivity_t> connectivity;
|
|
1075
1132
|
misaligned_ref_gt<max_level_t> max_level;
|
|
1076
1133
|
misaligned_ref_gt<vector_alignment_t> vector_alignment;
|
|
1077
1134
|
misaligned_ref_gt<bytes_per_label_t> bytes_per_label;
|
|
1078
1135
|
misaligned_ref_gt<bytes_per_id_t> bytes_per_id;
|
|
1136
|
+
misaligned_ref_gt<scalar_kind_t> scalar_kind;
|
|
1079
1137
|
misaligned_ref_gt<size_t> size;
|
|
1080
1138
|
misaligned_ref_gt<entry_idx_t> entry_idx;
|
|
1081
1139
|
|
|
1140
|
+
// Additional:
|
|
1141
|
+
misaligned_ref_gt<size_t> bytes_for_graphs;
|
|
1142
|
+
misaligned_ref_gt<size_t> bytes_for_vectors;
|
|
1143
|
+
misaligned_ref_gt<size_t> bytes_checksum;
|
|
1144
|
+
|
|
1082
1145
|
file_head_t(byte_t* ptr) noexcept
|
|
1083
1146
|
: magic((char const*)exchange(ptr, ptr + sizeof(magic_t))),
|
|
1084
1147
|
version_major(exchange(ptr, ptr + sizeof(version_major_t))),
|
|
@@ -1088,8 +1151,55 @@ struct file_head_t {
|
|
|
1088
1151
|
max_level(exchange(ptr, ptr + sizeof(max_level_t))),
|
|
1089
1152
|
vector_alignment(exchange(ptr, ptr + sizeof(vector_alignment_t))),
|
|
1090
1153
|
bytes_per_label(exchange(ptr, ptr + sizeof(bytes_per_label_t))),
|
|
1091
|
-
bytes_per_id(exchange(ptr, ptr + sizeof(bytes_per_id_t))),
|
|
1092
|
-
|
|
1154
|
+
bytes_per_id(exchange(ptr, ptr + sizeof(bytes_per_id_t))),
|
|
1155
|
+
scalar_kind(exchange(ptr, ptr + sizeof(scalar_kind_t))), size(exchange(ptr, ptr + sizeof(size_t))),
|
|
1156
|
+
entry_idx(exchange(ptr, ptr + sizeof(entry_idx_t))), bytes_for_graphs(exchange(ptr, ptr + sizeof(size_t))),
|
|
1157
|
+
bytes_for_vectors(exchange(ptr, ptr + sizeof(size_t))), bytes_checksum(exchange(ptr, ptr + sizeof(size_t))) {}
|
|
1158
|
+
};
|
|
1159
|
+
|
|
1160
|
+
struct file_head_result_t {
|
|
1161
|
+
|
|
1162
|
+
using magic_t = file_head_t::magic_t;
|
|
1163
|
+
using version_major_t = file_head_t::version_major_t;
|
|
1164
|
+
using version_minor_t = file_head_t::version_minor_t;
|
|
1165
|
+
using version_patch_t = file_head_t::version_patch_t;
|
|
1166
|
+
|
|
1167
|
+
using connectivity_t = file_head_t::connectivity_t;
|
|
1168
|
+
using max_level_t = file_head_t::max_level_t;
|
|
1169
|
+
using vector_alignment_t = file_head_t::vector_alignment_t;
|
|
1170
|
+
using bytes_per_label_t = file_head_t::bytes_per_label_t;
|
|
1171
|
+
using bytes_per_id_t = file_head_t::bytes_per_id_t;
|
|
1172
|
+
using size_t = file_head_t::size_t;
|
|
1173
|
+
using entry_idx_t = file_head_t::entry_idx_t;
|
|
1174
|
+
|
|
1175
|
+
// Versioning:
|
|
1176
|
+
version_major_t version_major;
|
|
1177
|
+
version_minor_t version_minor;
|
|
1178
|
+
version_patch_t version_patch;
|
|
1179
|
+
|
|
1180
|
+
// Structural:
|
|
1181
|
+
metric_kind_t metric;
|
|
1182
|
+
connectivity_t connectivity;
|
|
1183
|
+
max_level_t max_level;
|
|
1184
|
+
vector_alignment_t vector_alignment;
|
|
1185
|
+
bytes_per_label_t bytes_per_label;
|
|
1186
|
+
bytes_per_id_t bytes_per_id;
|
|
1187
|
+
scalar_kind_t scalar_kind;
|
|
1188
|
+
size_t size;
|
|
1189
|
+
entry_idx_t entry_idx;
|
|
1190
|
+
|
|
1191
|
+
// Additional:
|
|
1192
|
+
size_t bytes_for_graphs;
|
|
1193
|
+
size_t bytes_for_vectors;
|
|
1194
|
+
size_t bytes_checksum;
|
|
1195
|
+
|
|
1196
|
+
error_t error;
|
|
1197
|
+
|
|
1198
|
+
explicit operator bool() const noexcept { return !error; }
|
|
1199
|
+
file_head_result_t failed(char const* message) noexcept {
|
|
1200
|
+
error = message;
|
|
1201
|
+
return std::move(*this);
|
|
1202
|
+
}
|
|
1093
1203
|
};
|
|
1094
1204
|
|
|
1095
1205
|
static_assert(sizeof(file_header_t) == 64, "File header should be exactly 64 bytes");
|
|
@@ -1103,6 +1213,24 @@ using return_type_gt =
|
|
|
1103
1213
|
typename std::result_of<metric_at(args_at...)>::type;
|
|
1104
1214
|
#endif
|
|
1105
1215
|
|
|
1216
|
+
/// @brief OS-specific to wrap open file-descriptors/handles.
|
|
1217
|
+
#if defined(USEARCH_DEFINED_WINDOWS)
|
|
1218
|
+
struct viewed_file_t {
|
|
1219
|
+
HANDLE file_handle{};
|
|
1220
|
+
HANDLE mapping_handle{};
|
|
1221
|
+
void* ptr{};
|
|
1222
|
+
size_t length{};
|
|
1223
|
+
explicit operator bool() const noexcept { return mapping_handle != nullptr; }
|
|
1224
|
+
};
|
|
1225
|
+
#else
|
|
1226
|
+
struct viewed_file_t {
|
|
1227
|
+
int file_descriptor{};
|
|
1228
|
+
void* ptr{};
|
|
1229
|
+
size_t length{};
|
|
1230
|
+
explicit operator bool() const noexcept { return file_descriptor != 0; }
|
|
1231
|
+
};
|
|
1232
|
+
#endif
|
|
1233
|
+
|
|
1106
1234
|
/**
|
|
1107
1235
|
* @brief Approximate Nearest Neighbors Search index using the
|
|
1108
1236
|
* Hierarchical Navigable Small World graph algorithm.
|
|
@@ -1168,18 +1296,17 @@ using return_type_gt =
|
|
|
1168
1296
|
template <typename metric_at = ip_gt<float>, //
|
|
1169
1297
|
typename label_at = std::size_t, //
|
|
1170
1298
|
typename id_at = std::uint32_t, //
|
|
1171
|
-
typename scalar_at = float, //
|
|
1172
1299
|
typename allocator_at = std::allocator<char>, //
|
|
1173
1300
|
typename point_allocator_at = allocator_at> //
|
|
1174
1301
|
class index_gt {
|
|
1175
1302
|
public:
|
|
1176
1303
|
using metric_t = metric_at;
|
|
1177
|
-
using scalar_t = scalar_at;
|
|
1178
1304
|
using label_t = label_at;
|
|
1179
1305
|
using id_t = id_at;
|
|
1180
1306
|
using allocator_t = allocator_at;
|
|
1181
1307
|
using point_allocator_t = point_allocator_at;
|
|
1182
1308
|
|
|
1309
|
+
using scalar_t = typename metric_t::scalar_t;
|
|
1183
1310
|
using vector_view_t = span_gt<scalar_t const>;
|
|
1184
1311
|
using distance_t = return_type_gt<metric_t, vector_view_t, vector_view_t>;
|
|
1185
1312
|
|
|
@@ -1390,7 +1517,7 @@ class index_gt {
|
|
|
1390
1517
|
allocator_t allocator_{};
|
|
1391
1518
|
point_allocator_t point_allocator_{};
|
|
1392
1519
|
precomputed_constants_t pre_{};
|
|
1393
|
-
|
|
1520
|
+
viewed_file_t viewed_file_{};
|
|
1394
1521
|
|
|
1395
1522
|
usearch_align_m mutable std::atomic<std::size_t> capacity_{};
|
|
1396
1523
|
usearch_align_m mutable std::atomic<std::size_t> size_{};
|
|
@@ -1415,7 +1542,7 @@ class index_gt {
|
|
|
1415
1542
|
std::size_t max_level() const noexcept { return static_cast<std::size_t>(max_level_); }
|
|
1416
1543
|
index_config_t const& config() const noexcept { return config_; }
|
|
1417
1544
|
index_limits_t const& limits() const noexcept { return limits_; }
|
|
1418
|
-
bool is_immutable() const noexcept { return
|
|
1545
|
+
bool is_immutable() const noexcept { return bool(viewed_file_); }
|
|
1419
1546
|
|
|
1420
1547
|
/**
|
|
1421
1548
|
* @section Exceptions
|
|
@@ -1424,8 +1551,8 @@ class index_gt {
|
|
|
1424
1551
|
explicit index_gt(index_config_t config = {}, metric_t metric = {}, allocator_t allocator = {},
|
|
1425
1552
|
point_allocator_t point_allocator = {}) noexcept
|
|
1426
1553
|
: config_(config), limits_(0, 0), metric_(metric), allocator_(std::move(allocator)),
|
|
1427
|
-
point_allocator_(std::move(point_allocator)), pre_(precompute_(config)),
|
|
1428
|
-
|
|
1554
|
+
point_allocator_(std::move(point_allocator)), pre_(precompute_(config)), size_(0u), max_level_(-1),
|
|
1555
|
+
entry_id_(0u), nodes_(nullptr), nodes_mutexes_(), contexts_(nullptr) {}
|
|
1429
1556
|
|
|
1430
1557
|
/**
|
|
1431
1558
|
* @brief Clones the structure with the same hyper-parameters, but without contents.
|
|
@@ -1453,6 +1580,7 @@ class index_gt {
|
|
|
1453
1580
|
/**
|
|
1454
1581
|
* @brief Erases all the vectors from the index.
|
|
1455
1582
|
* Will change `size()` to zero, but will keep the same `capacity()`.
|
|
1583
|
+
* Will keep the number of available threads/contexts the same as it was.
|
|
1456
1584
|
*/
|
|
1457
1585
|
void clear() noexcept {
|
|
1458
1586
|
std::size_t n = size_;
|
|
@@ -1466,6 +1594,7 @@ class index_gt {
|
|
|
1466
1594
|
/**
|
|
1467
1595
|
* @brief Erases all the vectors from the index, also deallocating the registry.
|
|
1468
1596
|
* Will change both `size()` and `capacity()` to zero.
|
|
1597
|
+
* Will deallocate all threads/contexts.
|
|
1469
1598
|
*/
|
|
1470
1599
|
void reset() noexcept {
|
|
1471
1600
|
clear();
|
|
@@ -1476,6 +1605,7 @@ class index_gt {
|
|
|
1476
1605
|
context_allocator_t{}.deallocate(exchange(contexts_, nullptr), limits_.threads());
|
|
1477
1606
|
limits_ = index_limits_t{0, 0};
|
|
1478
1607
|
capacity_ = 0;
|
|
1608
|
+
reset_view_();
|
|
1479
1609
|
}
|
|
1480
1610
|
|
|
1481
1611
|
/**
|
|
@@ -1487,7 +1617,7 @@ class index_gt {
|
|
|
1487
1617
|
std::swap(metric_, other.metric_);
|
|
1488
1618
|
std::swap(allocator_, other.allocator_);
|
|
1489
1619
|
std::swap(pre_, other.pre_);
|
|
1490
|
-
std::swap(
|
|
1620
|
+
std::swap(viewed_file_, other.viewed_file_);
|
|
1491
1621
|
std::swap(max_level_, other.max_level_);
|
|
1492
1622
|
std::swap(entry_id_, other.entry_id_);
|
|
1493
1623
|
std::swap(nodes_, other.nodes_);
|
|
@@ -1613,6 +1743,11 @@ class index_gt {
|
|
|
1613
1743
|
std::size_t measurements{};
|
|
1614
1744
|
error_t error{};
|
|
1615
1745
|
|
|
1746
|
+
// todo: this is a hack to get a handle to a failed search_result_t without changing its member types
|
|
1747
|
+
// given that search_result_t can represent a failed result (which implies no valid top_candidates_t &top_),
|
|
1748
|
+
// the top_ probably needs to be a pointer.
|
|
1749
|
+
inline search_result_t(index_gt const& index) noexcept
|
|
1750
|
+
: index_(index), top_(index.contexts_[0].top_candidates) {}
|
|
1616
1751
|
inline search_result_t(search_result_t&&) = default;
|
|
1617
1752
|
inline search_result_t& operator=(search_result_t&&) = default;
|
|
1618
1753
|
|
|
@@ -1854,7 +1989,7 @@ class index_gt {
|
|
|
1854
1989
|
*/
|
|
1855
1990
|
std::size_t memory_usage(std::size_t allocator_entry_bytes = default_allocator_entry_bytes()) const noexcept {
|
|
1856
1991
|
std::size_t total = 0;
|
|
1857
|
-
if (
|
|
1992
|
+
if (!viewed_file_) {
|
|
1858
1993
|
stats_t s = stats();
|
|
1859
1994
|
total += s.allocated_bytes;
|
|
1860
1995
|
total += s.nodes * allocator_entry_bytes;
|
|
@@ -1895,17 +2030,23 @@ class index_gt {
|
|
|
1895
2030
|
*/
|
|
1896
2031
|
serialization_result_t save(char const* file_path) const noexcept {
|
|
1897
2032
|
|
|
2033
|
+
// Make sure we have right to write to that file
|
|
1898
2034
|
serialization_result_t result;
|
|
2035
|
+
std::FILE* file = std::fopen(file_path, "wb");
|
|
2036
|
+
if (!file)
|
|
2037
|
+
return result.failed(std::strerror(errno));
|
|
2038
|
+
|
|
2039
|
+
// Prepare the header with metadata
|
|
1899
2040
|
file_header_t state_buffer{};
|
|
1900
2041
|
std::memset(state_buffer, 0, sizeof(file_header_t));
|
|
1901
2042
|
file_head_t state{state_buffer};
|
|
1902
2043
|
std::memcpy(state_buffer, default_magic(), std::strlen(default_magic()));
|
|
1903
2044
|
|
|
1904
|
-
//
|
|
2045
|
+
// Mark compatibility
|
|
1905
2046
|
state.version_major = USEARCH_VERSION_MAJOR;
|
|
1906
2047
|
state.version_minor = USEARCH_VERSION_MINOR;
|
|
1907
2048
|
state.version_patch = USEARCH_VERSION_PATCH;
|
|
1908
|
-
state.metric =
|
|
2049
|
+
state.metric = metric_.kind();
|
|
1909
2050
|
|
|
1910
2051
|
// Describe state
|
|
1911
2052
|
state.connectivity = config_.connectivity;
|
|
@@ -1913,13 +2054,25 @@ class index_gt {
|
|
|
1913
2054
|
state.vector_alignment = config_.vector_alignment;
|
|
1914
2055
|
state.bytes_per_label = sizeof(label_t);
|
|
1915
2056
|
state.bytes_per_id = sizeof(id_t);
|
|
2057
|
+
state.scalar_kind = metric_.scalar_kind();
|
|
1916
2058
|
state.size = size_;
|
|
1917
2059
|
state.entry_idx = entry_id_;
|
|
1918
2060
|
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
2061
|
+
// Augment with metadata
|
|
2062
|
+
std::size_t graphs_bytes = 0;
|
|
2063
|
+
std::size_t vectors_bytes = 0;
|
|
2064
|
+
for (std::size_t i = 0; i != state.size; ++i) {
|
|
2065
|
+
node_t node = node_with_id_(i);
|
|
2066
|
+
std::size_t node_bytes = node_bytes_(node);
|
|
2067
|
+
std::size_t node_vector_bytes = node_vector_bytes_(node);
|
|
2068
|
+
graphs_bytes += node_bytes - node_vector_bytes;
|
|
2069
|
+
vectors_bytes += node_vector_bytes;
|
|
2070
|
+
}
|
|
2071
|
+
state.bytes_for_graphs = graphs_bytes;
|
|
2072
|
+
state.bytes_for_vectors = vectors_bytes;
|
|
2073
|
+
state.bytes_checksum = 0;
|
|
1922
2074
|
|
|
2075
|
+
// Perform serialization
|
|
1923
2076
|
auto write_chunk = [&](void* begin, std::size_t length) {
|
|
1924
2077
|
std::size_t written = std::fwrite(begin, length, 1, file);
|
|
1925
2078
|
if (!written) {
|
|
@@ -2027,7 +2180,7 @@ class index_gt {
|
|
|
2027
2180
|
}
|
|
2028
2181
|
|
|
2029
2182
|
std::fclose(file);
|
|
2030
|
-
|
|
2183
|
+
reset_view_();
|
|
2031
2184
|
return {};
|
|
2032
2185
|
}
|
|
2033
2186
|
|
|
@@ -2038,14 +2191,38 @@ class index_gt {
|
|
|
2038
2191
|
*/
|
|
2039
2192
|
serialization_result_t view(char const* file_path) noexcept {
|
|
2040
2193
|
serialization_result_t result;
|
|
2194
|
+
|
|
2041
2195
|
#if defined(USEARCH_DEFINED_WINDOWS)
|
|
2042
|
-
|
|
2196
|
+
|
|
2197
|
+
HANDLE file_handle =
|
|
2198
|
+
CreateFile(file_path, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
|
|
2199
|
+
if (file_handle == INVALID_HANDLE_VALUE)
|
|
2200
|
+
return result.failed("Opening file failed!");
|
|
2201
|
+
|
|
2202
|
+
size_t file_length = GetFileSize(file_handle, 0);
|
|
2203
|
+
HANDLE mapping_handle = CreateFileMapping(file_handle, 0, PAGE_READONLY, 0, 0, 0);
|
|
2204
|
+
if (mapping_handle == 0) {
|
|
2205
|
+
CloseHandle(file_handle);
|
|
2206
|
+
return result.failed("Mapping file failed!");
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
byte_t* file = (byte_t*)MapViewOfFile(mapping_handle, FILE_MAP_READ, 0, 0, file_length);
|
|
2210
|
+
if (file == 0) {
|
|
2211
|
+
CloseHandle(mapping_handle);
|
|
2212
|
+
CloseHandle(file_handle);
|
|
2213
|
+
return result.failed("View the map failed!");
|
|
2214
|
+
}
|
|
2215
|
+
viewed_file_.file_handle = file_handle;
|
|
2216
|
+
viewed_file_.mapping_handle = mapping_handle;
|
|
2217
|
+
viewed_file_.ptr = file;
|
|
2218
|
+
viewed_file_.length = file_length;
|
|
2219
|
+
#else
|
|
2220
|
+
|
|
2221
|
+
#if defined(USEARCH_DEFINED_LINUX)
|
|
2222
|
+
int descriptor = open(file_path, O_RDONLY | O_NOATIME);
|
|
2043
2223
|
#else
|
|
2044
|
-
int
|
|
2045
|
-
#if __linux__
|
|
2046
|
-
open_flags |= O_NOATIME;
|
|
2224
|
+
int descriptor = open(file_path, O_RDONLY);
|
|
2047
2225
|
#endif
|
|
2048
|
-
int descriptor = open(file_path, open_flags);
|
|
2049
2226
|
|
|
2050
2227
|
// Estimate the file size
|
|
2051
2228
|
struct stat file_stat;
|
|
@@ -2061,16 +2238,20 @@ class index_gt {
|
|
|
2061
2238
|
close(descriptor);
|
|
2062
2239
|
return result.failed(std::strerror(errno));
|
|
2063
2240
|
}
|
|
2241
|
+
viewed_file_.file_descriptor = descriptor;
|
|
2242
|
+
viewed_file_.ptr = file;
|
|
2243
|
+
viewed_file_.length = file_stat.st_size;
|
|
2244
|
+
#endif // Platform specific code
|
|
2064
2245
|
|
|
2065
2246
|
// Read the header
|
|
2066
2247
|
{
|
|
2067
2248
|
file_head_t state{file};
|
|
2068
2249
|
if (state.bytes_per_label != sizeof(label_t)) {
|
|
2069
|
-
|
|
2250
|
+
reset_view_();
|
|
2070
2251
|
return result.failed("Incompatible label type!");
|
|
2071
2252
|
}
|
|
2072
2253
|
if (state.bytes_per_id != sizeof(id_t)) {
|
|
2073
|
-
|
|
2254
|
+
reset_view_();
|
|
2074
2255
|
return result.failed("Incompatible ID type!");
|
|
2075
2256
|
}
|
|
2076
2257
|
|
|
@@ -2103,14 +2284,27 @@ class index_gt {
|
|
|
2103
2284
|
max_level_ = (std::max)(max_level_, level);
|
|
2104
2285
|
}
|
|
2105
2286
|
|
|
2106
|
-
viewed_file_descriptor_ = descriptor;
|
|
2107
2287
|
return {};
|
|
2108
|
-
#endif // POSIX
|
|
2109
2288
|
}
|
|
2110
2289
|
|
|
2111
2290
|
#pragma endregion
|
|
2112
2291
|
|
|
2113
2292
|
private:
|
|
2293
|
+
void reset_view_() noexcept {
|
|
2294
|
+
if (!viewed_file_)
|
|
2295
|
+
return;
|
|
2296
|
+
#if defined(USEARCH_DEFINED_WINDOWS)
|
|
2297
|
+
UnmapViewOfFile(viewed_file_.ptr);
|
|
2298
|
+
CloseHandle(viewed_file_.mapping_handle);
|
|
2299
|
+
CloseHandle(viewed_file_.file_handle);
|
|
2300
|
+
|
|
2301
|
+
#else
|
|
2302
|
+
munmap(viewed_file_.ptr, viewed_file_.length);
|
|
2303
|
+
close(viewed_file_.file_descriptor);
|
|
2304
|
+
#endif
|
|
2305
|
+
viewed_file_ = {};
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2114
2308
|
inline static precomputed_constants_t precompute_(index_config_t const& config) noexcept {
|
|
2115
2309
|
precomputed_constants_t pre;
|
|
2116
2310
|
pre.connectivity_max_base = config.connectivity * base_level_multiple_();
|
|
@@ -2135,7 +2329,7 @@ class index_gt {
|
|
|
2135
2329
|
|
|
2136
2330
|
void node_free_(std::size_t id) noexcept {
|
|
2137
2331
|
|
|
2138
|
-
if (
|
|
2332
|
+
if (viewed_file_)
|
|
2139
2333
|
return;
|
|
2140
2334
|
|
|
2141
2335
|
node_t& node = nodes_[id];
|
|
@@ -2396,6 +2590,7 @@ class index_gt {
|
|
|
2396
2590
|
void search_exact_(vector_view_t query, std::size_t count, context_t& context) const noexcept {
|
|
2397
2591
|
top_candidates_t& top = context.top_candidates;
|
|
2398
2592
|
top.clear();
|
|
2593
|
+
top.reserve(count);
|
|
2399
2594
|
for (std::size_t i = 0; i != size(); ++i)
|
|
2400
2595
|
top.insert({context.measure(query, node_with_id_(i)), static_cast<id_t>(i)}, count);
|
|
2401
2596
|
}
|
|
@@ -2444,6 +2639,47 @@ class index_gt {
|
|
|
2444
2639
|
}
|
|
2445
2640
|
};
|
|
2446
2641
|
|
|
2642
|
+
/**
|
|
2643
|
+
* @brief Extracts metadata from pre-constructed index on disk, without loading it
|
|
2644
|
+
* or mapping the whole binary file.
|
|
2645
|
+
*/
|
|
2646
|
+
inline file_head_result_t index_metadata(char const* file_path) noexcept {
|
|
2647
|
+
|
|
2648
|
+
file_head_result_t result;
|
|
2649
|
+
file_header_t state_buffer{};
|
|
2650
|
+
std::FILE* file = std::fopen(file_path, "rb");
|
|
2651
|
+
if (!file)
|
|
2652
|
+
return result.failed(std::strerror(errno));
|
|
2653
|
+
|
|
2654
|
+
// Read the header
|
|
2655
|
+
std::size_t read = std::fread(&state_buffer[0], sizeof(file_header_t), 1, file);
|
|
2656
|
+
std::fclose(file);
|
|
2657
|
+
if (!read)
|
|
2658
|
+
return result.failed(std::strerror(errno));
|
|
2659
|
+
|
|
2660
|
+
// Parse and validate the MIME type
|
|
2661
|
+
file_head_t state{state_buffer};
|
|
2662
|
+
if (std::strncmp(state.magic, default_magic(), std::strlen(default_magic())) != 0)
|
|
2663
|
+
return result.failed("Wrong MIME type!");
|
|
2664
|
+
|
|
2665
|
+
result.version_major = state.version_major;
|
|
2666
|
+
result.version_minor = state.version_minor;
|
|
2667
|
+
result.version_patch = state.version_patch;
|
|
2668
|
+
result.metric = state.metric;
|
|
2669
|
+
result.connectivity = state.connectivity;
|
|
2670
|
+
result.max_level = state.max_level;
|
|
2671
|
+
result.vector_alignment = state.vector_alignment;
|
|
2672
|
+
result.bytes_per_label = state.bytes_per_label;
|
|
2673
|
+
result.bytes_per_id = state.bytes_per_id;
|
|
2674
|
+
result.scalar_kind = state.scalar_kind;
|
|
2675
|
+
result.size = state.size;
|
|
2676
|
+
result.entry_idx = state.entry_idx;
|
|
2677
|
+
result.bytes_for_graphs = state.bytes_for_graphs;
|
|
2678
|
+
result.bytes_for_vectors = state.bytes_for_vectors;
|
|
2679
|
+
result.bytes_checksum = state.bytes_checksum;
|
|
2680
|
+
return result;
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2447
2683
|
} // namespace usearch
|
|
2448
2684
|
} // namespace unum
|
|
2449
2685
|
|