usearch 1.2.2 → 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.
@@ -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 index_dense_metadata(char const* file_path) noexcept {
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 = index_dense_metadata(path);
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 `stream_length` for the length of the binary serialized representation.
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
- bool get(key_t key, b1x8_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to_b1x8); }
546
- bool get(key_t key, i8_bits_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to_i8); }
547
- bool get(key_t key, f16_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to_f16); }
548
- bool get(key_t key, f32_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to_f32); }
549
- bool get(key_t key, f64_t* vector, std::size_t vectors_count = 1) const { return get_(key, vector, vectors_count, casts_.to_f64); }
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 stream(output_callback_at&& callback, serialization_config_t config = {}) const {
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 (!callback(&dimensions, sizeof(dimensions)))
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 (!callback(&dimensions, sizeof(dimensions)))
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 (!callback(vector, matrix_cols))
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 (!callback(&buffer, sizeof(buffer)))
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_->stream(std::forward<output_callback_at>(callback));
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 stream_length(serialization_config_t config = {}) const noexcept {
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_->stream_length();
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
- serialization_result_t load(input_file_t file, serialization_config_t config = {}) {
856
+ template <typename input_callback_at>
857
+ serialization_result_t load_from_stream(input_callback_at&& input, serialization_config_t config = {}) {
822
858
 
823
- serialization_result_t result = file.open_if_not();
824
- if (!result)
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
- result = file.read(&dimensions, sizeof(dimensions));
836
- if (!result)
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
- result = file.read(&dimensions, sizeof(dimensions));
843
- if (!result)
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
- result = file.read(vector, matrix_cols);
853
- if (!result)
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
- result = file.read(buffer, sizeof(buffer));
863
- if (!result)
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_->load(std::move(file));
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
- #if defined(USEARCH_DEFINED_WINDOWS)
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 > last_capacity_)) {
769
- std::size_t new_cap = last_capacity_ * capacity_multiplier();
770
- #if defined(USEARCH_DEFINED_WINDOWS)
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.2.2",
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.0",
28
+ "semantic-release": "^21.1.1",
29
29
  "typescript": "^5.1.6"
30
30
  }
31
31
  }