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.
@@ -155,7 +155,7 @@ There are a few ways to compile the C 99 USearch SDK.
155
155
  Using the Makefile:
156
156
 
157
157
  ```sh
158
- make -C ./c libusearch_c.so -C ./c libusearch_c.so
158
+ make -C ./c libusearch_c.so
159
159
  ```
160
160
 
161
161
  Using CMake:
@@ -857,7 +857,7 @@ class growing_hash_set_gt {
857
857
  std::size_t size() const noexcept { return count_; }
858
858
 
859
859
  void clear() noexcept {
860
- std::memset(slots_, 0xFF, capacity_ * sizeof(element_t));
860
+ std::memset((void*)slots_, 0xFF, capacity_ * sizeof(element_t));
861
861
  count_ = 0;
862
862
  }
863
863
 
@@ -930,7 +930,7 @@ class growing_hash_set_gt {
930
930
  if (!new_slots)
931
931
  return false;
932
932
 
933
- std::memset(new_slots, 0xFF, new_capacity * sizeof(element_t));
933
+ std::memset((void*)new_slots, 0xFF, new_capacity * sizeof(element_t));
934
934
  std::size_t new_count = count_;
935
935
  if (count_) {
936
936
  for (std::size_t old_index = 0; old_index != capacity_; ++old_index) {
@@ -1353,7 +1353,7 @@ class output_file_t {
1353
1353
  return result.failed(std::strerror(errno));
1354
1354
  return result;
1355
1355
  }
1356
- serialization_result_t write(void* begin, std::size_t length) noexcept {
1356
+ serialization_result_t write(void const* begin, std::size_t length) noexcept {
1357
1357
  serialization_result_t result;
1358
1358
  std::size_t written = std::fwrite(begin, length, 1, file_);
1359
1359
  if (!written)
@@ -1440,6 +1440,7 @@ class memory_mapped_file_t {
1440
1440
 
1441
1441
  public:
1442
1442
  explicit operator bool() const noexcept { return ptr_ != nullptr; }
1443
+ byte_t* data() noexcept { return reinterpret_cast<byte_t*>(ptr_); }
1443
1444
  byte_t const* data() const noexcept { return reinterpret_cast<byte_t const*>(ptr_); }
1444
1445
  std::size_t size() const noexcept { return static_cast<std::size_t>(length_); }
1445
1446
 
@@ -2540,6 +2541,8 @@ class index_gt {
2540
2541
  // Normalize stats
2541
2542
  result.computed_distances = context.computed_distances_count - result.computed_distances;
2542
2543
  result.visited_members = context.iteration_cycles - result.visited_members;
2544
+
2545
+ (void)predicate;
2543
2546
  return result;
2544
2547
  }
2545
2548
 
@@ -2591,11 +2594,45 @@ class index_gt {
2591
2594
  return result;
2592
2595
  }
2593
2596
 
2597
+ stats_t stats(stats_t* stats_per_level, std::size_t max_level) const noexcept {
2598
+
2599
+ std::size_t head_bytes = node_head_bytes_();
2600
+ for (std::size_t i = 0; i != size(); ++i) {
2601
+ node_t node = node_at_(i);
2602
+
2603
+ stats_per_level[0].nodes++;
2604
+ stats_per_level[0].edges += neighbors_(node, 0).size();
2605
+ stats_per_level[0].allocated_bytes += pre_.neighbors_base_bytes + head_bytes;
2606
+
2607
+ level_t node_level = static_cast<level_t>(node.level());
2608
+ for (level_t l = 1; l <= (std::min)(node_level, static_cast<level_t>(max_level)); ++l) {
2609
+ stats_per_level[l].nodes++;
2610
+ stats_per_level[l].edges += neighbors_(node, l).size();
2611
+ stats_per_level[l].allocated_bytes += pre_.neighbors_bytes;
2612
+ }
2613
+ }
2614
+
2615
+ // The `max_edges` parameter can be inferred from `nodes`
2616
+ stats_per_level[0].max_edges = stats_per_level[0].nodes * config_.connectivity_base;
2617
+ for (std::size_t l = 1; l <= max_level; ++l)
2618
+ stats_per_level[l].max_edges = stats_per_level[l].nodes * config_.connectivity;
2619
+
2620
+ // Aggregate stats across levels
2621
+ stats_t result{};
2622
+ for (std::size_t l = 0; l <= max_level; ++l)
2623
+ result.nodes += stats_per_level[l].nodes, //
2624
+ result.edges += stats_per_level[l].edges, //
2625
+ result.allocated_bytes += stats_per_level[l].allocated_bytes, //
2626
+ result.max_edges += stats_per_level[l].max_edges; //
2627
+
2628
+ return result;
2629
+ }
2630
+
2594
2631
  /**
2595
2632
  * @brief A relatively accurate lower bound on the amount of memory consumed by the system.
2596
2633
  * In practice it's error will be below 10%.
2597
2634
  *
2598
- * @see `stream_length` for the length of the binary serialized representation.
2635
+ * @see `serialized_length` for the length of the binary serialized representation.
2599
2636
  */
2600
2637
  std::size_t memory_usage(std::size_t allocator_entry_bytes = default_allocator_entry_bytes()) const noexcept {
2601
2638
  std::size_t total = 0;
@@ -2620,27 +2657,20 @@ class index_gt {
2620
2657
  #pragma region Serialization
2621
2658
 
2622
2659
  /**
2623
- * @brief Saves serialized binary index representation to a file, generally on disk.
2660
+ * @brief Estimate the binary length (in bytes) of the serialized index.
2624
2661
  */
2625
- template <typename progress_at = dummy_progress_t>
2626
- serialization_result_t save(output_file_t file, progress_at&& progress = {}) const noexcept {
2627
-
2628
- serialization_result_t result = file.open_if_not();
2629
- if (result)
2630
- stream(
2631
- [&](void* buffer, std::size_t length) {
2632
- result = file.write(buffer, length);
2633
- return !!result;
2634
- },
2635
- std::forward<progress_at>(progress));
2636
- return result;
2662
+ std::size_t serialized_length() const noexcept {
2663
+ std::size_t neighbors_length = 0;
2664
+ for (std::size_t i = 0; i != size(); ++i)
2665
+ neighbors_length += node_bytes_(node_at_(i).level()) + sizeof(level_t);
2666
+ return sizeof(index_serialized_header_t) + neighbors_length;
2637
2667
  }
2638
2668
 
2639
2669
  /**
2640
2670
  * @brief Saves serialized binary index representation to a stream.
2641
2671
  */
2642
2672
  template <typename output_callback_at, typename progress_at = dummy_progress_t>
2643
- serialization_result_t stream(output_callback_at&& callback, progress_at&& progress = {}) const noexcept {
2673
+ serialization_result_t save_to_stream(output_callback_at&& output, progress_at&& progress = {}) const noexcept {
2644
2674
 
2645
2675
  serialization_result_t result;
2646
2676
 
@@ -2651,8 +2681,8 @@ class index_gt {
2651
2681
  header.connectivity_base = config_.connectivity_base;
2652
2682
  header.max_level = max_level_;
2653
2683
  header.entry_slot = entry_slot_;
2654
- if (!callback(&header, sizeof(header)))
2655
- return result.failed("Failed to serialize into stream");
2684
+ if (!output(&header, sizeof(header)))
2685
+ return result.failed("Failed to serialize the header into stream");
2656
2686
 
2657
2687
  // Export the number of levels per node
2658
2688
  // That is both enough to estimate the overall memory consumption,
@@ -2660,15 +2690,15 @@ class index_gt {
2660
2690
  for (std::size_t i = 0; i != header.size; ++i) {
2661
2691
  node_t node = node_at_(i);
2662
2692
  level_t level = node.level();
2663
- if (!callback(&level, sizeof(level)))
2664
- return result.failed("Failed to serialize into stream");
2693
+ if (!output(&level, sizeof(level)))
2694
+ return result.failed("Failed to serialize nodes levels into stream");
2665
2695
  }
2666
2696
 
2667
2697
  // After that dump the nodes themselves
2668
2698
  for (std::size_t i = 0; i != header.size; ++i) {
2669
2699
  span_bytes_t node_bytes = node_bytes_(node_at_(i));
2670
- if (!callback(node_bytes.data(), node_bytes.size()))
2671
- return result.failed("Failed to serialize into stream");
2700
+ if (!output(node_bytes.data(), node_bytes.size()))
2701
+ return result.failed("Failed to serialize nodes into stream");
2672
2702
  progress(i, header.size);
2673
2703
  }
2674
2704
 
@@ -2676,36 +2706,22 @@ class index_gt {
2676
2706
  }
2677
2707
 
2678
2708
  /**
2679
- * @brief Estimate the binary length (in bytes) of the serialized index.
2709
+ * @brief Symmetric to `save_from_stream`, pulls data from a stream.
2680
2710
  */
2681
- std::size_t stream_length() const noexcept {
2682
- std::size_t neighbors_length = 0;
2683
- for (std::size_t i = 0; i != size(); ++i)
2684
- neighbors_length += node_bytes_(node_at_(i).level()) + sizeof(level_t);
2685
- return sizeof(index_serialized_header_t) + neighbors_length;
2686
- }
2711
+ template <typename input_callback_at, typename progress_at = dummy_progress_t>
2712
+ serialization_result_t load_from_stream(input_callback_at&& input, progress_at&& progress = {}) noexcept {
2687
2713
 
2688
- /**
2689
- * @brief Loads the serialized binary index representation from disk to RAM.
2690
- * Adjusts the configuration properties of the constructed index to
2691
- * match the settings in the file.
2692
- */
2693
- template <typename progress_at = dummy_progress_t>
2694
- serialization_result_t load(input_file_t file, progress_at&& progress = {}) noexcept {
2714
+ serialization_result_t result;
2695
2715
 
2696
2716
  // Remove previously stored objects
2697
2717
  reset();
2698
2718
 
2699
- serialization_result_t result = file.open_if_not();
2700
- if (!result)
2701
- return result;
2702
-
2703
2719
  // Pull basic metadata
2704
2720
  index_serialized_header_t header;
2705
- result = file.read(&header, sizeof(header));
2706
- if (!result)
2707
- return result;
2721
+ if (!input(&header, sizeof(header)))
2722
+ return result.failed("Failed to pull the header from the stream");
2708
2723
 
2724
+ // We are loading an empty index, no more work to do
2709
2725
  if (!header.size) {
2710
2726
  reset();
2711
2727
  return result;
@@ -2716,9 +2732,8 @@ class index_gt {
2716
2732
  buffer_gt<level_t, levels_allocator_t> levels(header.size);
2717
2733
  if (!levels)
2718
2734
  return result.failed("Out of memory");
2719
- result = file.read(levels, header.size * sizeof(level_t));
2720
- if (!result)
2721
- return result;
2735
+ if (!input(levels, header.size * sizeof(level_t)))
2736
+ return result.failed("Failed to pull nodes levels from the stream");
2722
2737
 
2723
2738
  // Submit metadata
2724
2739
  config_.connectivity = header.connectivity;
@@ -2737,10 +2752,9 @@ class index_gt {
2737
2752
  // Load the nodes
2738
2753
  for (std::size_t i = 0; i != header.size; ++i) {
2739
2754
  span_bytes_t node_bytes = node_malloc_(levels[i]);
2740
- result = file.read(node_bytes.data(), node_bytes.size());
2741
- if (!result) {
2755
+ if (!input(node_bytes.data(), node_bytes.size())) {
2742
2756
  reset();
2743
- return result;
2757
+ return result.failed("Failed to pull nodes from the stream");
2744
2758
  }
2745
2759
  nodes_[i] = node_t{node_bytes.data()};
2746
2760
  progress(i, header.size);
@@ -2748,6 +2762,113 @@ class index_gt {
2748
2762
  return {};
2749
2763
  }
2750
2764
 
2765
+ template <typename progress_at = dummy_progress_t>
2766
+ serialization_result_t save(char const* file_path, progress_at&& progress = {}) const noexcept {
2767
+ return save(output_file_t(file_path), std::forward<progress_at>(progress));
2768
+ }
2769
+
2770
+ template <typename progress_at = dummy_progress_t>
2771
+ serialization_result_t load(char const* file_path, progress_at&& progress = {}) noexcept {
2772
+ return load(input_file_t(file_path), std::forward<progress_at>(progress));
2773
+ }
2774
+
2775
+ /**
2776
+ * @brief Saves serialized binary index representation to a file, generally on disk.
2777
+ */
2778
+ template <typename progress_at = dummy_progress_t>
2779
+ serialization_result_t save(output_file_t file, progress_at&& progress = {}) const noexcept {
2780
+
2781
+ serialization_result_t io_result = file.open_if_not();
2782
+ if (!io_result)
2783
+ return io_result;
2784
+
2785
+ serialization_result_t stream_result = save_to_stream(
2786
+ [&](void* buffer, std::size_t length) {
2787
+ io_result = file.write(buffer, length);
2788
+ return !!io_result;
2789
+ },
2790
+ std::forward<progress_at>(progress));
2791
+
2792
+ if (!stream_result)
2793
+ return stream_result;
2794
+ return io_result;
2795
+ }
2796
+
2797
+ /**
2798
+ * @brief Memory-maps the serialized binary index representation from disk,
2799
+ * @b without copying data into RAM, and fetching it on-demand.
2800
+ */
2801
+ template <typename progress_at = dummy_progress_t>
2802
+ serialization_result_t save(memory_mapped_file_t file, std::size_t offset = 0,
2803
+ progress_at&& progress = {}) const noexcept {
2804
+
2805
+ serialization_result_t io_result = file.open_if_not();
2806
+ if (!io_result)
2807
+ return io_result;
2808
+
2809
+ serialization_result_t stream_result = save_to_stream(
2810
+ [&](void* buffer, std::size_t length) {
2811
+ if (offset + length > file.size())
2812
+ return false;
2813
+ std::memcpy(file.data() + offset, buffer, length);
2814
+ offset += length;
2815
+ return true;
2816
+ },
2817
+ std::forward<progress_at>(progress));
2818
+
2819
+ return stream_result;
2820
+ }
2821
+
2822
+ /**
2823
+ * @brief Loads the serialized binary index representation from disk to RAM.
2824
+ * Adjusts the configuration properties of the constructed index to
2825
+ * match the settings in the file.
2826
+ */
2827
+ template <typename progress_at = dummy_progress_t>
2828
+ serialization_result_t load(input_file_t file, progress_at&& progress = {}) noexcept {
2829
+
2830
+ serialization_result_t io_result = file.open_if_not();
2831
+ if (!io_result)
2832
+ return io_result;
2833
+
2834
+ serialization_result_t stream_result = load_from_stream(
2835
+ [&](void* buffer, std::size_t length) {
2836
+ io_result = file.read(buffer, length);
2837
+ return !!io_result;
2838
+ },
2839
+ std::forward<progress_at>(progress));
2840
+
2841
+ if (!stream_result)
2842
+ return stream_result;
2843
+ return io_result;
2844
+ }
2845
+
2846
+ /**
2847
+ * @brief Loads the serialized binary index representation from disk to RAM.
2848
+ * Adjusts the configuration properties of the constructed index to
2849
+ * match the settings in the file.
2850
+ */
2851
+ template <typename progress_at = dummy_progress_t>
2852
+ serialization_result_t load(memory_mapped_file_t file, std::size_t offset = 0,
2853
+ progress_at&& progress = {}) noexcept {
2854
+
2855
+ serialization_result_t io_result = file.open_if_not();
2856
+ if (!io_result)
2857
+ return io_result;
2858
+
2859
+ serialization_result_t stream_result = load_from_stream(
2860
+ [&](void* buffer, std::size_t length) {
2861
+ if (offset + length > file.size())
2862
+ return false;
2863
+ std::memcpy(buffer, file.data() + offset, length);
2864
+ offset += length;
2865
+ return true;
2866
+ },
2867
+ std::forward<progress_at>(progress));
2868
+
2869
+ return stream_result;
2870
+ }
2871
+
2751
2872
  /**
2752
2873
  * @brief Memory-maps the serialized binary index representation from disk,
2753
2874
  * @b without copying data into RAM, and fetching it on-demand.
@@ -3522,7 +3643,6 @@ static join_result_t join( //
3522
3643
  config.max_proposals = (std::min)(men.size(), config.max_proposals);
3523
3644
 
3524
3645
  using distance_t = typename men_at::distance_t;
3525
- using dynamic_allocator_t = typename men_at::dynamic_allocator_t;
3526
3646
  using dynamic_allocator_traits_t = typename men_at::dynamic_allocator_traits_t;
3527
3647
  using man_key_t = typename men_at::key_t;
3528
3648
  using woman_key_t = typename women_at::key_t;