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.
- 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 +172 -52
- package/include/usearch/index_dense.hpp +194 -57
- package/include/usearch/index_plugins.hpp +34 -20
- package/package.json +2 -2
- 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
package/docs/compilation.md
CHANGED
|
@@ -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 `
|
|
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
|
|
2660
|
+
* @brief Estimate the binary length (in bytes) of the serialized index.
|
|
2624
2661
|
*/
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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
|
|
2709
|
+
* @brief Symmetric to `save_from_stream`, pulls data from a stream.
|
|
2680
2710
|
*/
|
|
2681
|
-
|
|
2682
|
-
|
|
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
|
-
|
|
2706
|
-
|
|
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
|
-
|
|
2720
|
-
|
|
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
|
-
|
|
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;
|