usearch 1.2.1 → 1.3.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/prerelease.yml +26 -0
- package/.github/workflows/release.yml +1 -1
- package/CMakeLists.txt +1 -0
- package/Cargo.toml +1 -1
- package/README.md +16 -10
- package/c/lib.cpp +35 -1
- package/c/test.c +117 -114
- package/c/usearch.h +28 -1
- package/conanfile.py +1 -1
- package/cpp/test.cpp +2 -2
- package/docs/benchmarks.md +6 -6
- package/docs/compilation.md +1 -1
- package/include/usearch/index.hpp +173 -53
- package/include/usearch/index_dense.hpp +194 -57
- package/include/usearch/index_plugins.hpp +34 -20
- package/package.json +2 -2
- package/pyproject.toml +1 -1
- package/python/lib.cpp +93 -31
- package/python/scripts/test_index.py +38 -10
- package/python/usearch/index.py +87 -37
- package/rust/README.md +2 -2
- package/rust/lib.cpp +15 -4
- package/rust/lib.hpp +5 -0
- package/rust/lib.rs +75 -43
- package/wasmer.toml +1 -1
|
@@ -154,10 +154,10 @@ struct index_dense_metadata_result_t {
|
|
|
154
154
|
};
|
|
155
155
|
|
|
156
156
|
/**
|
|
157
|
-
* @brief Extracts metadata from pre-constructed index on disk,
|
|
157
|
+
* @brief Extracts metadata from a pre-constructed index on disk,
|
|
158
158
|
* without loading it or mapping the whole binary file.
|
|
159
159
|
*/
|
|
160
|
-
inline index_dense_metadata_result_t
|
|
160
|
+
inline index_dense_metadata_result_t index_dense_metadata_from_path(char const* file_path) noexcept {
|
|
161
161
|
index_dense_metadata_result_t result;
|
|
162
162
|
std::unique_ptr<std::FILE, int (*)(std::FILE*)> file(std::fopen(file_path, "rb"), &std::fclose);
|
|
163
163
|
if (!file)
|
|
@@ -219,6 +219,56 @@ inline index_dense_metadata_result_t index_dense_metadata(char const* file_path)
|
|
|
219
219
|
return result.failed("Not a dense USearch index!");
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
/**
|
|
223
|
+
* @brief Extracts metadata from a pre-constructed index serialized into an in-memory buffer.
|
|
224
|
+
*/
|
|
225
|
+
inline index_dense_metadata_result_t index_dense_metadata_from_buffer(memory_mapped_file_t file,
|
|
226
|
+
std::size_t offset = 0) noexcept {
|
|
227
|
+
index_dense_metadata_result_t result;
|
|
228
|
+
|
|
229
|
+
// Read the header
|
|
230
|
+
if (offset + sizeof(index_dense_head_buffer_t) >= file.size())
|
|
231
|
+
return result.failed("End of file reached!");
|
|
232
|
+
|
|
233
|
+
byte_t* const file_data = file.data() + offset;
|
|
234
|
+
std::size_t const file_size = file.size() - offset;
|
|
235
|
+
std::memcpy(&result.head_buffer, file_data, sizeof(index_dense_head_buffer_t));
|
|
236
|
+
|
|
237
|
+
// Check if the file immediately starts with the index, instead of vectors
|
|
238
|
+
result.config.exclude_vectors = true;
|
|
239
|
+
if (std::memcmp(result.head_buffer, default_magic(), std::strlen(default_magic())) == 0)
|
|
240
|
+
return result;
|
|
241
|
+
|
|
242
|
+
// Check if it starts with 32-bit
|
|
243
|
+
std::uint32_t dimensions_u32[2]{0};
|
|
244
|
+
std::memcpy(dimensions_u32, result.head_buffer, sizeof(dimensions_u32));
|
|
245
|
+
std::size_t offset_if_u32 = std::size_t(dimensions_u32[0]) * dimensions_u32[1] + sizeof(dimensions_u32);
|
|
246
|
+
|
|
247
|
+
std::uint64_t dimensions_u64[2]{0};
|
|
248
|
+
std::memcpy(dimensions_u64, result.head_buffer, sizeof(dimensions_u64));
|
|
249
|
+
std::size_t offset_if_u64 = std::size_t(dimensions_u64[0]) * dimensions_u64[1] + sizeof(dimensions_u64);
|
|
250
|
+
|
|
251
|
+
// Check if it starts with 32-bit
|
|
252
|
+
if (offset_if_u32 + sizeof(index_dense_head_buffer_t) < file_size) {
|
|
253
|
+
std::memcpy(&result.head_buffer, file_data + offset_if_u32, sizeof(index_dense_head_buffer_t));
|
|
254
|
+
result.config.exclude_vectors = false;
|
|
255
|
+
result.config.use_64_bit_dimensions = false;
|
|
256
|
+
if (std::memcmp(result.head_buffer, default_magic(), std::strlen(default_magic())) == 0)
|
|
257
|
+
return result;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Check if it starts with 64-bit
|
|
261
|
+
if (offset_if_u64 + sizeof(index_dense_head_buffer_t) < file_size) {
|
|
262
|
+
std::memcpy(&result.head_buffer, file_data + offset_if_u64, sizeof(index_dense_head_buffer_t));
|
|
263
|
+
result.config.exclude_vectors = false;
|
|
264
|
+
result.config.use_64_bit_dimensions = true;
|
|
265
|
+
if (std::memcmp(result.head_buffer, default_magic(), std::strlen(default_magic())) == 0)
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return result.failed("Not a dense USearch index!");
|
|
270
|
+
}
|
|
271
|
+
|
|
222
272
|
/**
|
|
223
273
|
* @brief Oversimplified type-punned index for equidimensional vectors
|
|
224
274
|
* with automatic @b down-casting, hardware-specific @b SIMD metrics,
|
|
@@ -454,7 +504,7 @@ class index_dense_gt {
|
|
|
454
504
|
}
|
|
455
505
|
|
|
456
506
|
static index_dense_gt make(char const* path, bool view = false) {
|
|
457
|
-
index_dense_metadata_result_t meta =
|
|
507
|
+
index_dense_metadata_result_t meta = index_dense_metadata_from_path(path);
|
|
458
508
|
if (!meta)
|
|
459
509
|
return {};
|
|
460
510
|
metric_punned_t metric(meta.head.dimensions, meta.head.kind_metric, meta.head.kind_scalar);
|
|
@@ -501,6 +551,9 @@ class index_dense_gt {
|
|
|
501
551
|
|
|
502
552
|
stats_t stats() const { return typed_->stats(); }
|
|
503
553
|
stats_t stats(std::size_t level) const { return typed_->stats(level); }
|
|
554
|
+
stats_t stats(stats_t* stats_per_level, std::size_t max_level) const {
|
|
555
|
+
return typed_->stats(stats_per_level, max_level);
|
|
556
|
+
}
|
|
504
557
|
|
|
505
558
|
dynamic_allocator_t const& allocator() const { return typed_->dynamic_allocator(); }
|
|
506
559
|
key_t const& free_key() const { return free_key_; }
|
|
@@ -509,7 +562,7 @@ class index_dense_gt {
|
|
|
509
562
|
* @brief A relatively accurate lower bound on the amount of memory consumed by the system.
|
|
510
563
|
* In practice it's error will be below 10%.
|
|
511
564
|
*
|
|
512
|
-
* @see `
|
|
565
|
+
* @see `serialized_length` for the length of the binary serialized representation.
|
|
513
566
|
*/
|
|
514
567
|
std::size_t memory_usage() const {
|
|
515
568
|
return //
|
|
@@ -542,11 +595,11 @@ class index_dense_gt {
|
|
|
542
595
|
search_result_t search(f32_t const* vector, std::size_t wanted, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, thread, exact, casts_.from_f32); }
|
|
543
596
|
search_result_t search(f64_t const* vector, std::size_t wanted, std::size_t thread = any_thread(), bool exact = false) const { return search_(vector, wanted, thread, exact, casts_.from_f64); }
|
|
544
597
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
598
|
+
std::size_t get(key_t key, b1x8_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to_b1x8); }
|
|
599
|
+
std::size_t get(key_t key, i8_bits_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to_i8); }
|
|
600
|
+
std::size_t get(key_t key, f16_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to_f16); }
|
|
601
|
+
std::size_t get(key_t key, f32_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to_f32); }
|
|
602
|
+
std::size_t get(key_t key, f64_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to_f64); }
|
|
550
603
|
|
|
551
604
|
cluster_result_t cluster(b1x8_t const* vector, std::size_t level, std::size_t thread = any_thread()) const { return cluster_(vector, level, thread, casts_.from_b1x8); }
|
|
552
605
|
cluster_result_t cluster(i8_bits_t const* vector, std::size_t level, std::size_t thread = any_thread()) const { return cluster_(vector, level, thread, casts_.from_i8); }
|
|
@@ -710,29 +763,11 @@ class index_dense_gt {
|
|
|
710
763
|
std::iota(available_threads_.begin(), available_threads_.end(), 0ul);
|
|
711
764
|
}
|
|
712
765
|
|
|
713
|
-
/**
|
|
714
|
-
* @brief Saves the index to a file.
|
|
715
|
-
* @param[in] path The path to the file.
|
|
716
|
-
* @param[in] config Configuration parameters for exports.
|
|
717
|
-
* @return Outcome descriptor explicitly convertible to boolean.
|
|
718
|
-
*/
|
|
719
|
-
serialization_result_t save(output_file_t file, serialization_config_t config = {}) const {
|
|
720
|
-
serialization_result_t result = file.open_if_not();
|
|
721
|
-
if (result)
|
|
722
|
-
stream(
|
|
723
|
-
[&](void* buffer, std::size_t length) {
|
|
724
|
-
result = file.write(buffer, length);
|
|
725
|
-
return !!result;
|
|
726
|
-
},
|
|
727
|
-
config);
|
|
728
|
-
return result;
|
|
729
|
-
}
|
|
730
|
-
|
|
731
766
|
/**
|
|
732
767
|
* @brief Saves serialized binary index representation to a stream.
|
|
733
768
|
*/
|
|
734
769
|
template <typename output_callback_at, typename progress_at = dummy_progress_t>
|
|
735
|
-
serialization_result_t
|
|
770
|
+
serialization_result_t save_to_stream(output_callback_at&& output, serialization_config_t config = {}) const {
|
|
736
771
|
|
|
737
772
|
serialization_result_t result;
|
|
738
773
|
std::uint64_t matrix_rows = 0;
|
|
@@ -745,7 +780,7 @@ class index_dense_gt {
|
|
|
745
780
|
std::uint32_t dimensions[2];
|
|
746
781
|
dimensions[0] = static_cast<std::uint32_t>(typed_->size());
|
|
747
782
|
dimensions[1] = static_cast<std::uint32_t>(metric_.bytes_per_vector());
|
|
748
|
-
if (!
|
|
783
|
+
if (!output(&dimensions, sizeof(dimensions)))
|
|
749
784
|
return result.failed("Failed to serialize into stream");
|
|
750
785
|
matrix_rows = dimensions[0];
|
|
751
786
|
matrix_cols = dimensions[1];
|
|
@@ -753,7 +788,7 @@ class index_dense_gt {
|
|
|
753
788
|
std::uint64_t dimensions[2];
|
|
754
789
|
dimensions[0] = static_cast<std::uint64_t>(typed_->size());
|
|
755
790
|
dimensions[1] = static_cast<std::uint64_t>(metric_.bytes_per_vector());
|
|
756
|
-
if (!
|
|
791
|
+
if (!output(&dimensions, sizeof(dimensions)))
|
|
757
792
|
return result.failed("Failed to serialize into stream");
|
|
758
793
|
matrix_rows = dimensions[0];
|
|
759
794
|
matrix_cols = dimensions[1];
|
|
@@ -762,7 +797,7 @@ class index_dense_gt {
|
|
|
762
797
|
// Dump the vectors one after another
|
|
763
798
|
for (std::uint64_t i = 0; i != matrix_rows; ++i) {
|
|
764
799
|
byte_t* vector = vectors_lookup_[i];
|
|
765
|
-
if (!
|
|
800
|
+
if (!output(vector, matrix_cols))
|
|
766
801
|
return result.failed("Failed to serialize into stream");
|
|
767
802
|
}
|
|
768
803
|
}
|
|
@@ -791,25 +826,25 @@ class index_dense_gt {
|
|
|
791
826
|
head.dimensions = dimensions();
|
|
792
827
|
head.multi = multi();
|
|
793
828
|
|
|
794
|
-
if (!
|
|
829
|
+
if (!output(&buffer, sizeof(buffer)))
|
|
795
830
|
return result.failed("Failed to serialize into stream");
|
|
796
831
|
}
|
|
797
832
|
|
|
798
833
|
// Save the actual proximity graph
|
|
799
|
-
return typed_->
|
|
834
|
+
return typed_->save_to_stream(std::forward<output_callback_at>(output));
|
|
800
835
|
}
|
|
801
836
|
|
|
802
837
|
/**
|
|
803
838
|
* @brief Estimate the binary length (in bytes) of the serialized index.
|
|
804
839
|
*/
|
|
805
|
-
std::size_t
|
|
840
|
+
std::size_t serialized_length(serialization_config_t config = {}) const noexcept {
|
|
806
841
|
std::size_t dimensions_length = 0;
|
|
807
842
|
std::size_t matrix_length = 0;
|
|
808
843
|
if (!config.exclude_vectors) {
|
|
809
844
|
dimensions_length = config.use_64_bit_dimensions ? sizeof(std::uint64_t) * 2 : sizeof(std::uint32_t) * 2;
|
|
810
845
|
matrix_length = typed_->size() * metric_.bytes_per_vector();
|
|
811
846
|
}
|
|
812
|
-
return dimensions_length + matrix_length + sizeof(index_dense_head_buffer_t) + typed_->
|
|
847
|
+
return dimensions_length + matrix_length + sizeof(index_dense_head_buffer_t) + typed_->serialized_length();
|
|
813
848
|
}
|
|
814
849
|
|
|
815
850
|
/**
|
|
@@ -818,12 +853,14 @@ class index_dense_gt {
|
|
|
818
853
|
* @param[in] config Configuration parameters for imports.
|
|
819
854
|
* @return Outcome descriptor explicitly convertible to boolean.
|
|
820
855
|
*/
|
|
821
|
-
|
|
856
|
+
template <typename input_callback_at>
|
|
857
|
+
serialization_result_t load_from_stream(input_callback_at&& input, serialization_config_t config = {}) {
|
|
822
858
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
return result;
|
|
859
|
+
// Discard all previous memory allocations of `vectors_tape_allocator_`
|
|
860
|
+
reset();
|
|
826
861
|
|
|
862
|
+
// Infer the new index size
|
|
863
|
+
serialization_result_t result;
|
|
827
864
|
std::uint64_t matrix_rows = 0;
|
|
828
865
|
std::uint64_t matrix_cols = 0;
|
|
829
866
|
|
|
@@ -832,16 +869,14 @@ class index_dense_gt {
|
|
|
832
869
|
// Save the matrix size
|
|
833
870
|
if (!config.use_64_bit_dimensions) {
|
|
834
871
|
std::uint32_t dimensions[2];
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
return result;
|
|
872
|
+
if (!input(&dimensions, sizeof(dimensions)))
|
|
873
|
+
return result.failed("Failed to read 32-bit dimensions of the matrix");
|
|
838
874
|
matrix_rows = dimensions[0];
|
|
839
875
|
matrix_cols = dimensions[1];
|
|
840
876
|
} else {
|
|
841
877
|
std::uint64_t dimensions[2];
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
return result;
|
|
878
|
+
if (!input(&dimensions, sizeof(dimensions)))
|
|
879
|
+
return result.failed("Failed to read 64-bit dimensions of the matrix");
|
|
845
880
|
matrix_rows = dimensions[0];
|
|
846
881
|
matrix_cols = dimensions[1];
|
|
847
882
|
}
|
|
@@ -849,9 +884,8 @@ class index_dense_gt {
|
|
|
849
884
|
vectors_lookup_.resize(matrix_rows);
|
|
850
885
|
for (std::uint64_t slot = 0; slot != matrix_rows; ++slot) {
|
|
851
886
|
byte_t* vector = vectors_tape_allocator_.allocate(matrix_cols);
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
return result;
|
|
887
|
+
if (!input(vector, matrix_cols))
|
|
888
|
+
return result.failed("Failed to read vectors");
|
|
855
889
|
vectors_lookup_[slot] = vector;
|
|
856
890
|
}
|
|
857
891
|
}
|
|
@@ -859,9 +893,8 @@ class index_dense_gt {
|
|
|
859
893
|
// Load metadata and choose the right metric
|
|
860
894
|
{
|
|
861
895
|
index_dense_head_buffer_t buffer;
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
return result;
|
|
896
|
+
if (!input(buffer, sizeof(buffer)))
|
|
897
|
+
return result.failed("Failed to read the index ");
|
|
865
898
|
|
|
866
899
|
index_dense_head_t head{buffer};
|
|
867
900
|
if (std::memcmp(buffer, default_magic(), std::strlen(default_magic())) != 0)
|
|
@@ -882,7 +915,7 @@ class index_dense_gt {
|
|
|
882
915
|
}
|
|
883
916
|
|
|
884
917
|
// Pull the actual proximity graph
|
|
885
|
-
result = typed_->
|
|
918
|
+
result = typed_->load_from_stream(std::forward<input_callback_at>(input));
|
|
886
919
|
if (!result)
|
|
887
920
|
return result;
|
|
888
921
|
if (typed_->size() != static_cast<std::size_t>(matrix_rows))
|
|
@@ -900,10 +933,14 @@ class index_dense_gt {
|
|
|
900
933
|
*/
|
|
901
934
|
serialization_result_t view(memory_mapped_file_t file, std::size_t offset = 0, serialization_config_t config = {}) {
|
|
902
935
|
|
|
936
|
+
// Discard all previous memory allocations of `vectors_tape_allocator_`
|
|
937
|
+
reset();
|
|
938
|
+
|
|
903
939
|
serialization_result_t result = file.open_if_not();
|
|
904
940
|
if (!result)
|
|
905
941
|
return result;
|
|
906
942
|
|
|
943
|
+
// Infer the new index size
|
|
907
944
|
std::uint64_t matrix_rows = 0;
|
|
908
945
|
std::uint64_t matrix_cols = 0;
|
|
909
946
|
span_punned_t vectors_buffer;
|
|
@@ -976,6 +1013,109 @@ class index_dense_gt {
|
|
|
976
1013
|
return result;
|
|
977
1014
|
}
|
|
978
1015
|
|
|
1016
|
+
/**
|
|
1017
|
+
* @brief Saves the index to a file.
|
|
1018
|
+
* @param[in] path The path to the file.
|
|
1019
|
+
* @param[in] config Configuration parameters for exports.
|
|
1020
|
+
* @return Outcome descriptor explicitly convertible to boolean.
|
|
1021
|
+
*/
|
|
1022
|
+
serialization_result_t save(output_file_t file, serialization_config_t config = {}) const {
|
|
1023
|
+
|
|
1024
|
+
serialization_result_t io_result = file.open_if_not();
|
|
1025
|
+
if (!io_result)
|
|
1026
|
+
return io_result;
|
|
1027
|
+
|
|
1028
|
+
serialization_result_t stream_result = save_to_stream(
|
|
1029
|
+
[&](void const* buffer, std::size_t length) {
|
|
1030
|
+
io_result = file.write(buffer, length);
|
|
1031
|
+
return !!io_result;
|
|
1032
|
+
},
|
|
1033
|
+
config);
|
|
1034
|
+
|
|
1035
|
+
if (!stream_result)
|
|
1036
|
+
return stream_result;
|
|
1037
|
+
return io_result;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
/**
|
|
1041
|
+
* @brief Memory-maps the serialized binary index representation from disk,
|
|
1042
|
+
* @b without copying data into RAM, and fetching it on-demand.
|
|
1043
|
+
*/
|
|
1044
|
+
serialization_result_t save(memory_mapped_file_t file, std::size_t offset = 0,
|
|
1045
|
+
serialization_config_t config = {}) const {
|
|
1046
|
+
|
|
1047
|
+
serialization_result_t io_result = file.open_if_not();
|
|
1048
|
+
if (!io_result)
|
|
1049
|
+
return io_result;
|
|
1050
|
+
|
|
1051
|
+
serialization_result_t stream_result = save_to_stream(
|
|
1052
|
+
[&](void const* buffer, std::size_t length) {
|
|
1053
|
+
if (offset + length > file.size())
|
|
1054
|
+
return false;
|
|
1055
|
+
std::memcpy(file.data() + offset, buffer, length);
|
|
1056
|
+
offset += length;
|
|
1057
|
+
return true;
|
|
1058
|
+
},
|
|
1059
|
+
config);
|
|
1060
|
+
|
|
1061
|
+
return stream_result;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
/**
|
|
1065
|
+
* @brief Parses the index from file to RAM.
|
|
1066
|
+
* @param[in] path The path to the file.
|
|
1067
|
+
* @param[in] config Configuration parameters for imports.
|
|
1068
|
+
* @return Outcome descriptor explicitly convertible to boolean.
|
|
1069
|
+
*/
|
|
1070
|
+
serialization_result_t load(input_file_t file, serialization_config_t config = {}) {
|
|
1071
|
+
|
|
1072
|
+
serialization_result_t io_result = file.open_if_not();
|
|
1073
|
+
if (!io_result)
|
|
1074
|
+
return io_result;
|
|
1075
|
+
|
|
1076
|
+
serialization_result_t stream_result = load_from_stream(
|
|
1077
|
+
[&](void* buffer, std::size_t length) {
|
|
1078
|
+
io_result = file.read(buffer, length);
|
|
1079
|
+
return !!io_result;
|
|
1080
|
+
},
|
|
1081
|
+
config);
|
|
1082
|
+
|
|
1083
|
+
if (!stream_result)
|
|
1084
|
+
return stream_result;
|
|
1085
|
+
return io_result;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
/**
|
|
1089
|
+
* @brief Memory-maps the serialized binary index representation from disk,
|
|
1090
|
+
* @b without copying data into RAM, and fetching it on-demand.
|
|
1091
|
+
*/
|
|
1092
|
+
serialization_result_t load(memory_mapped_file_t file, std::size_t offset = 0, serialization_config_t config = {}) {
|
|
1093
|
+
|
|
1094
|
+
serialization_result_t io_result = file.open_if_not();
|
|
1095
|
+
if (!io_result)
|
|
1096
|
+
return io_result;
|
|
1097
|
+
|
|
1098
|
+
serialization_result_t stream_result = load_from_stream(
|
|
1099
|
+
[&](void* buffer, std::size_t length) {
|
|
1100
|
+
if (offset + length > file.size())
|
|
1101
|
+
return false;
|
|
1102
|
+
std::memcpy(buffer, file.data() + offset, length);
|
|
1103
|
+
offset += length;
|
|
1104
|
+
return true;
|
|
1105
|
+
},
|
|
1106
|
+
config);
|
|
1107
|
+
|
|
1108
|
+
return stream_result;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
serialization_result_t save(char const* file_path, serialization_config_t config = {}) const {
|
|
1112
|
+
return save(output_file_t(file_path), config);
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
serialization_result_t load(char const* file_path, serialization_config_t config = {}) {
|
|
1116
|
+
return load(input_file_t(file_path), config);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
979
1119
|
/**
|
|
980
1120
|
* @brief Checks if a vector with specified key is present.
|
|
981
1121
|
* @return `true` if the key is present in the index, `false` otherwise.
|
|
@@ -1489,6 +1629,8 @@ class index_dense_gt {
|
|
|
1489
1629
|
|
|
1490
1630
|
result.computed_distances = computed_distances;
|
|
1491
1631
|
result.visited_members = visited_members;
|
|
1632
|
+
|
|
1633
|
+
(void)progress;
|
|
1492
1634
|
return result;
|
|
1493
1635
|
}
|
|
1494
1636
|
|
|
@@ -1663,11 +1805,6 @@ class index_dense_gt {
|
|
|
1663
1805
|
return result;
|
|
1664
1806
|
}
|
|
1665
1807
|
|
|
1666
|
-
compressed_slot_t lookup_id_(key_t key) const {
|
|
1667
|
-
shared_lock_t lock(slot_lookup_mutex_);
|
|
1668
|
-
return slot_lookup_.at(key);
|
|
1669
|
-
}
|
|
1670
|
-
|
|
1671
1808
|
void reindex_keys_() {
|
|
1672
1809
|
|
|
1673
1810
|
// Estimate number of entries first
|
|
@@ -669,6 +669,34 @@ class aligned_allocator_gt {
|
|
|
669
669
|
|
|
670
670
|
using aligned_allocator_t = aligned_allocator_gt<>;
|
|
671
671
|
|
|
672
|
+
class page_allocator_t {
|
|
673
|
+
public:
|
|
674
|
+
static constexpr std::size_t page_size() { return 4096; }
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* @brief Allocates an @b uninitialized block of memory of the specified size.
|
|
678
|
+
* @param count_bytes The number of bytes to allocate.
|
|
679
|
+
* @return A pointer to the allocated memory block, or `nullptr` if allocation fails.
|
|
680
|
+
*/
|
|
681
|
+
byte_t* allocate(std::size_t count_bytes) const noexcept {
|
|
682
|
+
count_bytes = divide_round_up(count_bytes, page_size()) * page_size();
|
|
683
|
+
#if defined(USEARCH_DEFINED_WINDOWS)
|
|
684
|
+
return (byte_t*)(::VirtualAlloc(NULL, count_bytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE));
|
|
685
|
+
#else
|
|
686
|
+
return (byte_t*)mmap(NULL, count_bytes, PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
|
|
687
|
+
#endif
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
void deallocate(byte_t* page_pointer, std::size_t count_bytes) const noexcept {
|
|
691
|
+
#if defined(USEARCH_DEFINED_WINDOWS)
|
|
692
|
+
::VirtualFree(page_pointer, 0, MEM_RELEASE);
|
|
693
|
+
#else
|
|
694
|
+
count_bytes = divide_round_up(count_bytes, page_size()) * page_size();
|
|
695
|
+
munmap(page_pointer, count_bytes);
|
|
696
|
+
#endif
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
|
|
672
700
|
/**
|
|
673
701
|
* @brief Memory-mapping allocator designed for "alloc many, free at once" usage patterns.
|
|
674
702
|
* @b Thread-safe, @b except constructors and destructors.
|
|
@@ -718,15 +746,11 @@ template <std::size_t alignment_ak = 1> class memory_mapping_allocator_gt {
|
|
|
718
746
|
void reset() noexcept {
|
|
719
747
|
byte_t* last_arena = last_arena_;
|
|
720
748
|
while (last_arena) {
|
|
721
|
-
byte_t* previous_arena;
|
|
749
|
+
byte_t* previous_arena = nullptr;
|
|
722
750
|
std::memcpy(&previous_arena, last_arena, sizeof(byte_t*));
|
|
723
|
-
std::size_t last_cap;
|
|
751
|
+
std::size_t last_cap = 0;
|
|
724
752
|
std::memcpy(&last_cap, last_arena + sizeof(byte_t*), sizeof(std::size_t));
|
|
725
|
-
|
|
726
|
-
::VirtualFree(last_arena, 0, MEM_RELEASE);
|
|
727
|
-
#else
|
|
728
|
-
munmap(last_arena, last_cap);
|
|
729
|
-
#endif
|
|
753
|
+
page_allocator_t{}.deallocate(last_arena, last_cap);
|
|
730
754
|
last_arena = previous_arena;
|
|
731
755
|
}
|
|
732
756
|
|
|
@@ -760,22 +784,12 @@ template <std::size_t alignment_ak = 1> class memory_mapping_allocator_gt {
|
|
|
760
784
|
*/
|
|
761
785
|
inline byte_t* allocate(std::size_t count_bytes) noexcept {
|
|
762
786
|
std::size_t extended_bytes = divide_round_up<alignment_ak>(count_bytes) * alignment_ak;
|
|
763
|
-
// Check if the requested allocation size is absurd
|
|
764
|
-
if (extended_bytes > min_capacity())
|
|
765
|
-
return nullptr;
|
|
766
|
-
|
|
767
787
|
std::unique_lock<std::mutex> lock(mutex_);
|
|
768
|
-
if (!last_arena_ || (last_usage_ + extended_bytes
|
|
769
|
-
std::size_t new_cap = last_capacity_ * capacity_multiplier();
|
|
770
|
-
|
|
771
|
-
byte_t* new_arena = (byte_t*)(::VirtualAlloc(NULL, new_cap, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE));
|
|
772
|
-
if (new_arena == nullptr)
|
|
773
|
-
return nullptr;
|
|
774
|
-
#else
|
|
775
|
-
byte_t* new_arena = (byte_t*)mmap(NULL, new_cap, PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
|
|
788
|
+
if (!last_arena_ || (last_usage_ + extended_bytes >= last_capacity_)) {
|
|
789
|
+
std::size_t new_cap = (std::max)(last_capacity_, ceil2(extended_bytes)) * capacity_multiplier();
|
|
790
|
+
byte_t* new_arena = page_allocator_t{}.allocate(new_cap);
|
|
776
791
|
if (!new_arena)
|
|
777
792
|
return nullptr;
|
|
778
|
-
#endif
|
|
779
793
|
std::memcpy(new_arena, &last_arena_, sizeof(byte_t*));
|
|
780
794
|
std::memcpy(new_arena + sizeof(byte_t*), &new_cap, sizeof(std::size_t));
|
|
781
795
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "usearch",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Smaller & Faster Single-File Vector Search Engine from Unum",
|
|
5
5
|
"author": "Ash Vardanian",
|
|
6
6
|
"license": "Apache 2.0",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"@semantic-release/exec": "^6.0.3",
|
|
26
26
|
"@semantic-release/git": "^10.0.1",
|
|
27
27
|
"conventional-changelog-eslint": "^3.0.9",
|
|
28
|
-
"semantic-release": "^21.1.
|
|
28
|
+
"semantic-release": "^21.1.1",
|
|
29
29
|
"typescript": "^5.1.6"
|
|
30
30
|
}
|
|
31
31
|
}
|
package/pyproject.toml
CHANGED