usearch 0.22.2 → 0.22.3
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/.releaserc +2 -1
- package/.vscode/launch.json +5 -1
- package/.vscode/settings.json +3 -1
- package/Cargo.toml +1 -1
- package/README.md +6 -7
- package/conanfile.py +1 -1
- package/cpp/bench.cpp +2 -2
- package/cpp/test.cpp +4 -1
- package/docs/compilation.md +29 -7
- package/docs/cpp/index.rst +3 -3
- package/docs/cpp/reference.rst +13 -3
- package/docs/index.rst +8 -8
- package/docs/java/reference.rst +1 -1
- package/include/usearch/index.hpp +96 -47
- package/include/usearch/index_punned_dense.hpp +258 -128
- package/include/usearch/index_punned_helpers.hpp +150 -40
- package/javascript/README.md +1 -1
- package/package.json +1 -1
- package/pyproject.toml +3 -3
- package/python/lib.cpp +4 -0
- package/wasmer.toml +5 -1
package/.releaserc
CHANGED
package/.vscode/launch.json
CHANGED
|
@@ -13,7 +13,11 @@
|
|
|
13
13
|
"environment": [
|
|
14
14
|
{
|
|
15
15
|
"name": "ASAN_OPTIONS",
|
|
16
|
-
"value": "detect_leaks=0:atexit=1:strict_init_order=1:strict_string_checks=1"
|
|
16
|
+
"value": "detect_leaks=0:atexit=1:strict_init_order=1:strict_string_checks=1:abort_on_error=0"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "LSAN_OPTIONS",
|
|
20
|
+
"value": "verbosity=1:log_threads=1"
|
|
17
21
|
}
|
|
18
22
|
],
|
|
19
23
|
"stopAtEntry": false,
|
package/.vscode/settings.json
CHANGED
package/Cargo.toml
CHANGED
package/README.md
CHANGED
|
@@ -39,16 +39,15 @@ Linux • MacOS • Windows • Docker • WebAssembly
|
|
|
39
39
|
- ✅ Benchmark-topping performance.
|
|
40
40
|
- ✅ Simple and extensible [single C++11 header][usearch-header] implementation.
|
|
41
41
|
- ✅ SIMD-optimized and [user-defined metrics](#user-defined-functions) with JIT-compilation.
|
|
42
|
-
- ✅ Variable dimensionality vectors for unique applications, including
|
|
43
|
-
- ✅
|
|
44
|
-
- ✅
|
|
42
|
+
- ✅ Variable dimensionality vectors for unique applications, including search over compressed data.
|
|
43
|
+
- ✅ Bitwise Tanimoto and Sorensen coefficients for [Genomics and Chemistry applications](#usearch--rdkit--molecular-search).
|
|
44
|
+
- ✅ Hardware-agmostic `f16` & `f8` - [half-precision & quarter-precision support](#memory-efficiency-downcasting-and-quantization).
|
|
45
45
|
- ✅ [View large indexes from disk](#disk-based-indexes) without loading into RAM.
|
|
46
46
|
- ✅ Space-efficient point-clouds with `uint40_t`, accommodating 4B+ size.
|
|
47
|
-
- ✅
|
|
48
|
-
- ✅
|
|
47
|
+
- ✅ Compatible with OpenMP and custom "executors", for fine-grained control over CPU utilization.
|
|
48
|
+
- ✅ Supports multiple vectors per label.
|
|
49
49
|
- ✅ On-the-fly deletions.
|
|
50
|
-
- ✅
|
|
51
|
-
- ✅ Combine [USearch and RDKit for Molecule Search](#usearch--rdkit--molecular-search).
|
|
50
|
+
- ✅ [Semantic Search](#usearch--ai--multi-modal-semantic-search) and [Joins](#joins).
|
|
52
51
|
|
|
53
52
|
[usearch-header]: https://github.com/unum-cloud/usearch/blob/main/include/usearch/index.hpp
|
|
54
53
|
[obscure-use-cases]: https://ashvardanian.com/posts/abusing-vector-search
|
package/conanfile.py
CHANGED
|
@@ -4,7 +4,7 @@ from conans import ConanFile
|
|
|
4
4
|
class USearchConan(ConanFile):
|
|
5
5
|
|
|
6
6
|
name = 'USearch'
|
|
7
|
-
version = '0.22.
|
|
7
|
+
version = '0.22.3'
|
|
8
8
|
license = 'Apache 2.0'
|
|
9
9
|
url = 'https://github.com/unum-cloud/usearch'
|
|
10
10
|
description = 'Smaller & Faster Single-File Vector Search Engine from Unum'
|
package/cpp/bench.cpp
CHANGED
|
@@ -329,7 +329,7 @@ static void single_shot(dataset_at& dataset, index_at& index, bool construct = t
|
|
|
329
329
|
using label_t = typename index_at::label_t;
|
|
330
330
|
using distance_t = typename index_at::distance_t;
|
|
331
331
|
using join_result_t = typename index_at::join_result_t;
|
|
332
|
-
constexpr
|
|
332
|
+
constexpr label_t missing_label = std::numeric_limits<label_t>::max();
|
|
333
333
|
|
|
334
334
|
std::printf("\n");
|
|
335
335
|
std::printf("------------\n");
|
|
@@ -383,7 +383,7 @@ static void single_shot(dataset_at& dataset, index_at& index, bool construct = t
|
|
|
383
383
|
// Evaluate join quality
|
|
384
384
|
std::size_t recall_join = 0, unmatched_count = 0;
|
|
385
385
|
for (std::size_t i = 0; i != index.size(); ++i) {
|
|
386
|
-
recall_join += man_to_woman[i] == i;
|
|
386
|
+
recall_join += man_to_woman[i] == static_cast<label_t>(i);
|
|
387
387
|
unmatched_count += man_to_woman[i] == missing_label;
|
|
388
388
|
}
|
|
389
389
|
std::printf("Recall Joins %.2f %%\n", recall_join * 100.f / index.size());
|
package/cpp/test.cpp
CHANGED
|
@@ -67,7 +67,8 @@ template <typename scalar_at, typename index_at> void test3d(index_at&& index) {
|
|
|
67
67
|
expect(std::abs(matched_distances[0]) < 0.01);
|
|
68
68
|
|
|
69
69
|
// Search again over mapped index
|
|
70
|
-
index_metadata("tmp.usearch");
|
|
70
|
+
file_head_result_t head = index_metadata("tmp.usearch");
|
|
71
|
+
expect(head.size == 3);
|
|
71
72
|
index.view("tmp.usearch");
|
|
72
73
|
matched_count = index.search(view_t{&vec[0], 3ul}, 5).dump_to(matched_labels, matched_distances);
|
|
73
74
|
expect(matched_count == 3);
|
|
@@ -144,6 +145,8 @@ int main(int, char**) {
|
|
|
144
145
|
static_assert(!std::is_same<index_gt<ip_gt<>>::value_type, std::true_type>());
|
|
145
146
|
static_assert(!std::is_same<index_gt<cos_gt<>>::value_type, std::true_type>());
|
|
146
147
|
static_assert(!std::is_same<index_gt<l2sq_gt<>>::value_type, std::true_type>());
|
|
148
|
+
|
|
149
|
+
static_assert(has_reset<memory_mapping_allocator_t>());
|
|
147
150
|
// static_assert(!std::is_same<index_gt<hamming_gt<>>::value_type, std::true_type>());
|
|
148
151
|
// static_assert(!std::is_same<index_gt<tanimoto_gt<>>::value_type, std::true_type>());
|
|
149
152
|
// static_assert(!std::is_same<index_gt<sorensen_gt<>>::value_type, std::true_type>());
|
package/docs/compilation.md
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# Compiling USearch
|
|
2
2
|
|
|
3
|
+
This document describes the workflow for advanced USearch users and enthusiasts willing to contribute to the project.
|
|
3
4
|
Before building the first time, please pull submodules.
|
|
4
5
|
|
|
5
6
|
```sh
|
|
6
7
|
git submodule update --init --recursive
|
|
7
8
|
```
|
|
8
9
|
|
|
9
|
-
## C++
|
|
10
|
+
## C++11
|
|
10
11
|
|
|
11
12
|
Linux:
|
|
12
13
|
|
|
@@ -49,6 +50,12 @@ cppcheck --enable=all --suppress=cstyleCast --suppress=unusedFunction \
|
|
|
49
50
|
include/index_punned_helpers.hpp
|
|
50
51
|
```
|
|
51
52
|
|
|
53
|
+
Testing:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
cmake -DCMAKE_CXX_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_BUILD_TYPE=Debug -B ./build_debug && make -C ./build_debug && ./build_debug/test
|
|
57
|
+
```
|
|
58
|
+
|
|
52
59
|
Benchmarking:
|
|
53
60
|
|
|
54
61
|
```sh
|
|
@@ -65,7 +72,7 @@ Benchmarking:
|
|
|
65
72
|
--cos
|
|
66
73
|
```
|
|
67
74
|
|
|
68
|
-
## Python
|
|
75
|
+
## Python 3
|
|
69
76
|
|
|
70
77
|
Use PyTest to validate the build.
|
|
71
78
|
The `-s` option will disable capturing the logs.
|
|
@@ -110,11 +117,26 @@ python python/scripts/bench.py connectivity ...
|
|
|
110
117
|
|
|
111
118
|
## JavaScript
|
|
112
119
|
|
|
120
|
+
Node.JS:
|
|
121
|
+
|
|
113
122
|
```sh
|
|
114
123
|
npm install && node javascript/test.js
|
|
115
124
|
npm publish
|
|
116
125
|
```
|
|
117
126
|
|
|
127
|
+
WebAssembly:
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
emcmake cmake -B ./build -DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS} -s TOTAL_MEMORY=64MB" && emmake make -C ./build
|
|
131
|
+
node ./build/test.js
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
If you don't yet have `emcmake` installed:
|
|
135
|
+
|
|
136
|
+
```sh
|
|
137
|
+
git clone https://github.com/emscripten-core/emsdk.git && ./emsdk/emsdk install latest && ./emsdk/emsdk activate latest && source ./emsdk/emsdk_env.sh
|
|
138
|
+
```
|
|
139
|
+
|
|
118
140
|
## Rust
|
|
119
141
|
|
|
120
142
|
```sh
|
|
@@ -176,9 +198,9 @@ swift build
|
|
|
176
198
|
swift test -v
|
|
177
199
|
```
|
|
178
200
|
|
|
179
|
-
##
|
|
201
|
+
## C99
|
|
180
202
|
|
|
181
|
-
|
|
203
|
+
Linux:
|
|
182
204
|
|
|
183
205
|
```sh
|
|
184
206
|
g++ -shared -fPIC lib.cpp -I ../include/ -I ../fp16/include/ -o libusearch.so
|
|
@@ -205,10 +227,10 @@ docker buildx create --use &&
|
|
|
205
227
|
--push .
|
|
206
228
|
```
|
|
207
229
|
|
|
208
|
-
|
|
230
|
+
## Sub-Modules
|
|
209
231
|
|
|
210
232
|
Extending metrics in SimSIMD:
|
|
211
233
|
|
|
212
|
-
```
|
|
234
|
+
```sh
|
|
213
235
|
git push --set-upstream https://github.com/ashvardanian/simsimd.git HEAD:main
|
|
214
|
-
```
|
|
236
|
+
```
|
package/docs/cpp/index.rst
CHANGED
package/docs/cpp/reference.rst
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
API Reference
|
|
2
2
|
===============
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
usearch
|
|
6
|
-
|
|
4
|
+
=================================
|
|
5
|
+
usearch/index.hpp
|
|
6
|
+
=================================
|
|
7
7
|
.. doxygenfile:: index.hpp
|
|
8
|
+
|
|
9
|
+
=================================
|
|
10
|
+
usearch/index_punned_helpers.hpp
|
|
11
|
+
=================================
|
|
12
|
+
.. doxygenfile:: index_punned_helpers.hpp
|
|
13
|
+
|
|
14
|
+
=================================
|
|
15
|
+
usearch/index_punned_dense.hpp
|
|
16
|
+
=================================
|
|
17
|
+
.. doxygenfile:: index_punned_dense.hpp
|
package/docs/index.rst
CHANGED
|
@@ -3,27 +3,27 @@ Overview
|
|
|
3
3
|
==========
|
|
4
4
|
.. mdinclude:: ../README.md
|
|
5
5
|
|
|
6
|
-
.. toctree::
|
|
7
|
-
:hidden:
|
|
8
|
-
:caption: �
|
|
9
|
-
|
|
10
|
-
benchmarks
|
|
11
|
-
compilation
|
|
12
|
-
|
|
13
6
|
.. toctree::
|
|
14
7
|
:hidden:
|
|
15
8
|
:caption: �
|
|
16
9
|
|
|
17
10
|
cpp/index
|
|
18
11
|
python/index
|
|
12
|
+
rust/index
|
|
19
13
|
javascript/index
|
|
20
14
|
java/index
|
|
21
|
-
rust/index
|
|
22
15
|
c/index
|
|
23
16
|
swift/index
|
|
24
17
|
golang/index
|
|
25
18
|
wolfram/index
|
|
26
19
|
|
|
20
|
+
.. toctree::
|
|
21
|
+
:hidden:
|
|
22
|
+
:caption: �
|
|
23
|
+
|
|
24
|
+
compilation
|
|
25
|
+
benchmarks
|
|
26
|
+
|
|
27
27
|
.. toctree::
|
|
28
28
|
:hidden:
|
|
29
29
|
:caption: �
|
package/docs/java/reference.rst
CHANGED
|
@@ -1451,9 +1451,32 @@ struct dummy_label_to_label_mapping_t {
|
|
|
1451
1451
|
template <typename label_at> member_ref_t operator[](label_at&&) const noexcept { return {}; }
|
|
1452
1452
|
};
|
|
1453
1453
|
|
|
1454
|
+
template <typename, typename at> struct has_reset_gt {
|
|
1455
|
+
static_assert(std::integral_constant<at, false>::value, "Second template parameter needs to be of function type.");
|
|
1456
|
+
};
|
|
1457
|
+
|
|
1458
|
+
template <typename check_at, typename return_at, typename... args_at>
|
|
1459
|
+
struct has_reset_gt<check_at, return_at(args_at...)> {
|
|
1460
|
+
private:
|
|
1461
|
+
template <typename at>
|
|
1462
|
+
static constexpr auto check(at*) ->
|
|
1463
|
+
typename std::is_same<decltype(std::declval<at>().reset(std::declval<args_at>()...)), return_at>::type;
|
|
1464
|
+
template <typename> static constexpr std::false_type check(...);
|
|
1465
|
+
|
|
1466
|
+
typedef decltype(check<check_at>(0)) type;
|
|
1467
|
+
|
|
1468
|
+
public:
|
|
1469
|
+
static constexpr bool value = type::value;
|
|
1470
|
+
};
|
|
1471
|
+
|
|
1472
|
+
/**
|
|
1473
|
+
* @brief Checks if a certain class has a member function called `reset`.
|
|
1474
|
+
*/
|
|
1475
|
+
template <typename at> constexpr bool has_reset() { return has_reset_gt<at, void()>::value; }
|
|
1476
|
+
|
|
1454
1477
|
/**
|
|
1455
1478
|
* @brief Approximate Nearest Neighbors Search index using the
|
|
1456
|
-
* Hierarchical Navigable Small World
|
|
1479
|
+
* Hierarchical Navigable Small World @b (HNSW) graphs algorithm.
|
|
1457
1480
|
*
|
|
1458
1481
|
* @section Features
|
|
1459
1482
|
* - Thread-safe for concurrent construction.
|
|
@@ -1473,15 +1496,22 @@ struct dummy_label_to_label_mapping_t {
|
|
|
1473
1496
|
* The smallest unsigned integer type to address indexed elements.
|
|
1474
1497
|
* Can be a built-in `uint32_t`, `uint64_t`, or our custom `uint40_t`.
|
|
1475
1498
|
*
|
|
1476
|
-
* @tparam
|
|
1477
|
-
* Dynamic memory allocator
|
|
1499
|
+
* @tparam dynamic_allocator_at
|
|
1500
|
+
* Dynamic memory allocator for temporary buffers, visits indicators, and
|
|
1501
|
+
* priority queues, needed during construction and traversals of graphs.
|
|
1502
|
+
* The allocated buffers may be uninitialized.
|
|
1503
|
+
*
|
|
1504
|
+
* @tparam tape_allocator_at
|
|
1505
|
+
* Potentially different memory allocator for primary allocations of nodes and vectors.
|
|
1506
|
+
* It would never `deallocate` separate entries, and would only free all the space at once.
|
|
1507
|
+
* The allocated buffers may be uninitialized.
|
|
1478
1508
|
*
|
|
1479
1509
|
* @section Usage
|
|
1480
1510
|
*
|
|
1481
1511
|
* @subsection Exceptions
|
|
1482
1512
|
*
|
|
1483
1513
|
* None of the methods throw exceptions in the "Release" compilation mode.
|
|
1484
|
-
* It may only `throw` if your memory ::
|
|
1514
|
+
* It may only `throw` if your memory ::dynamic_allocator_at or ::metric_at isn't
|
|
1485
1515
|
* safe to copy.
|
|
1486
1516
|
*
|
|
1487
1517
|
* @section Implementation Details
|
|
@@ -1513,18 +1543,18 @@ struct dummy_label_to_label_mapping_t {
|
|
|
1513
1543
|
* vectors. That, however, is solved by `index_punned_dense_gt`, which also adds automatic casting.
|
|
1514
1544
|
*
|
|
1515
1545
|
*/
|
|
1516
|
-
template <typename metric_at = ip_gt<float>,
|
|
1517
|
-
typename label_at = std::int64_t,
|
|
1518
|
-
typename id_at = std::uint32_t,
|
|
1519
|
-
typename
|
|
1520
|
-
typename
|
|
1546
|
+
template <typename metric_at = ip_gt<float>, //
|
|
1547
|
+
typename label_at = std::int64_t, //
|
|
1548
|
+
typename id_at = std::uint32_t, //
|
|
1549
|
+
typename dynamic_allocator_at = std::allocator<char>, //
|
|
1550
|
+
typename tape_allocator_at = dynamic_allocator_at> //
|
|
1521
1551
|
class index_gt {
|
|
1522
1552
|
public:
|
|
1523
1553
|
using metric_t = metric_at;
|
|
1524
1554
|
using label_t = label_at;
|
|
1525
1555
|
using id_t = id_at;
|
|
1526
|
-
using
|
|
1527
|
-
using
|
|
1556
|
+
using dynamic_allocator_t = dynamic_allocator_at;
|
|
1557
|
+
using tape_allocator_t = tape_allocator_at;
|
|
1528
1558
|
static_assert(sizeof(label_t) >= sizeof(id_t), "Having tiny labels doesn't make sense.");
|
|
1529
1559
|
|
|
1530
1560
|
using scalar_t = typename metric_t::scalar_t;
|
|
@@ -1601,7 +1631,7 @@ class index_gt {
|
|
|
1601
1631
|
|
|
1602
1632
|
// STL compatibility:
|
|
1603
1633
|
using value_type = std::pair<label_t, distance_t>;
|
|
1604
|
-
using allocator_type =
|
|
1634
|
+
using allocator_type = dynamic_allocator_t;
|
|
1605
1635
|
using size_type = std::size_t;
|
|
1606
1636
|
using difference_type = std::ptrdiff_t;
|
|
1607
1637
|
using reference = member_ref_t;
|
|
@@ -1618,13 +1648,14 @@ class index_gt {
|
|
|
1618
1648
|
using dim_t = std::uint32_t;
|
|
1619
1649
|
using level_t = std::int32_t;
|
|
1620
1650
|
|
|
1621
|
-
using allocator_traits_t = std::allocator_traits<
|
|
1622
|
-
using byte_t = typename
|
|
1623
|
-
static_assert(sizeof(byte_t) == 1,
|
|
1651
|
+
using allocator_traits_t = std::allocator_traits<dynamic_allocator_t>;
|
|
1652
|
+
using byte_t = typename dynamic_allocator_t::value_type;
|
|
1653
|
+
static_assert(sizeof(byte_t) == 1, //
|
|
1654
|
+
"Primary allocator must allocate separate addressable bytes");
|
|
1624
1655
|
|
|
1625
|
-
using
|
|
1626
|
-
static_assert(sizeof(typename
|
|
1627
|
-
"
|
|
1656
|
+
using tape_allocator_traits_t = std::allocator_traits<tape_allocator_t>;
|
|
1657
|
+
static_assert(sizeof(typename tape_allocator_traits_t::value_type) == 1, //
|
|
1658
|
+
"Tape allocator must allocate separate addressable bytes");
|
|
1628
1659
|
|
|
1629
1660
|
/**
|
|
1630
1661
|
* @brief How much larger (number of neighbors per node) will
|
|
@@ -1637,7 +1668,7 @@ class index_gt {
|
|
|
1637
1668
|
*/
|
|
1638
1669
|
static constexpr std::size_t node_head_bytes_() { return sizeof(label_t) + sizeof(dim_t) + sizeof(level_t); }
|
|
1639
1670
|
|
|
1640
|
-
using visits_bitset_t = visits_bitset_gt<
|
|
1671
|
+
using visits_bitset_t = visits_bitset_gt<dynamic_allocator_t>;
|
|
1641
1672
|
|
|
1642
1673
|
struct precomputed_constants_t {
|
|
1643
1674
|
double inverse_log_connectivity{};
|
|
@@ -1688,8 +1719,6 @@ class index_gt {
|
|
|
1688
1719
|
|
|
1689
1720
|
operator vector_view_t() const noexcept { return {vector(), dim()}; }
|
|
1690
1721
|
vector_view_t vector_view() const noexcept { return {vector(), dim()}; }
|
|
1691
|
-
explicit operator member_ref_t() noexcept { return {{tape_}, vector_view()}; }
|
|
1692
|
-
explicit operator member_cref_t() const noexcept { return {label(), vector_view()}; }
|
|
1693
1722
|
explicit operator bool() const noexcept { return tape_; }
|
|
1694
1723
|
};
|
|
1695
1724
|
|
|
@@ -1743,8 +1772,8 @@ class index_gt {
|
|
|
1743
1772
|
index_config_t config_{};
|
|
1744
1773
|
index_limits_t limits_{};
|
|
1745
1774
|
metric_t metric_{};
|
|
1746
|
-
mutable
|
|
1747
|
-
|
|
1775
|
+
mutable dynamic_allocator_t dynamic_allocator_{};
|
|
1776
|
+
tape_allocator_t tape_allocator_{};
|
|
1748
1777
|
precomputed_constants_t pre_{};
|
|
1749
1778
|
viewed_file_t viewed_file_{};
|
|
1750
1779
|
|
|
@@ -1757,11 +1786,11 @@ class index_gt {
|
|
|
1757
1786
|
level_t max_level_{};
|
|
1758
1787
|
id_t entry_id_{};
|
|
1759
1788
|
|
|
1760
|
-
using
|
|
1789
|
+
using nodes_allocator_t = typename allocator_traits_t::template rebind_alloc<node_t>;
|
|
1761
1790
|
node_t* nodes_{};
|
|
1762
1791
|
mutable visits_bitset_t nodes_mutexes_{};
|
|
1763
1792
|
|
|
1764
|
-
using
|
|
1793
|
+
using contexts_allocator_t = typename allocator_traits_t::template rebind_alloc<context_t>;
|
|
1765
1794
|
context_t* contexts_{};
|
|
1766
1795
|
|
|
1767
1796
|
public:
|
|
@@ -1777,16 +1806,16 @@ class index_gt {
|
|
|
1777
1806
|
* @section Exceptions
|
|
1778
1807
|
* Doesn't throw, unless the ::metric's and ::allocators's throw on copy-construction.
|
|
1779
1808
|
*/
|
|
1780
|
-
explicit index_gt(index_config_t config = {}, metric_t metric = {},
|
|
1781
|
-
|
|
1782
|
-
: config_(config), limits_(0, 0), metric_(metric),
|
|
1783
|
-
|
|
1809
|
+
explicit index_gt(index_config_t config = {}, metric_t metric = {}, dynamic_allocator_t allocator = {},
|
|
1810
|
+
tape_allocator_t tape_allocator = {}) noexcept
|
|
1811
|
+
: config_(config), limits_(0, 0), metric_(metric), dynamic_allocator_(std::move(allocator)),
|
|
1812
|
+
tape_allocator_(std::move(tape_allocator)), pre_(precompute_(config)), size_(0u), max_level_(-1),
|
|
1784
1813
|
entry_id_(0u), nodes_(nullptr), nodes_mutexes_(), contexts_(nullptr) {}
|
|
1785
1814
|
|
|
1786
1815
|
/**
|
|
1787
1816
|
* @brief Clones the structure with the same hyper-parameters, but without contents.
|
|
1788
1817
|
*/
|
|
1789
|
-
index_gt fork() noexcept { return index_gt{config_, metric_,
|
|
1818
|
+
index_gt fork() noexcept { return index_gt{config_, metric_, dynamic_allocator_}; }
|
|
1790
1819
|
|
|
1791
1820
|
~index_gt() noexcept { reset(); }
|
|
1792
1821
|
|
|
@@ -1811,7 +1840,7 @@ class index_gt {
|
|
|
1811
1840
|
copy_result_t copy(copy_config_t /*config*/ = {}) const noexcept {
|
|
1812
1841
|
copy_result_t result;
|
|
1813
1842
|
index_gt& other = result.index;
|
|
1814
|
-
other = index_gt(config_, metric_,
|
|
1843
|
+
other = index_gt(config_, metric_, dynamic_allocator_, tape_allocator_);
|
|
1815
1844
|
if (!other.reserve(limits_))
|
|
1816
1845
|
return result.failed("Failed to reserve the contexts");
|
|
1817
1846
|
|
|
@@ -1833,8 +1862,16 @@ class index_gt {
|
|
|
1833
1862
|
member_iterator_t begin() noexcept { return {this, 0}; }
|
|
1834
1863
|
member_iterator_t end() noexcept { return {this, size()}; }
|
|
1835
1864
|
|
|
1836
|
-
|
|
1837
|
-
|
|
1865
|
+
member_ref_t at(std::size_t i) noexcept {
|
|
1866
|
+
return {nodes_[i].label(), nodes_[i].vector_view(), static_cast<id_t>(i)};
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
member_cref_t at(std::size_t i) const noexcept {
|
|
1870
|
+
return {nodes_[i].label(), nodes_[i].vector_view(), static_cast<id_t>(i)};
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
dynamic_allocator_t const& dynamic_allocator() const noexcept { return dynamic_allocator_; }
|
|
1874
|
+
tape_allocator_t const& tape_allocator() const noexcept { return tape_allocator_; }
|
|
1838
1875
|
|
|
1839
1876
|
#pragma region Adjusting Configuration
|
|
1840
1877
|
|
|
@@ -1844,9 +1881,12 @@ class index_gt {
|
|
|
1844
1881
|
* Will keep the number of available threads/contexts the same as it was.
|
|
1845
1882
|
*/
|
|
1846
1883
|
void clear() noexcept {
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1884
|
+
if (!has_reset<tape_allocator_t>()) {
|
|
1885
|
+
std::size_t n = size_;
|
|
1886
|
+
for (std::size_t i = 0; i != n; ++i)
|
|
1887
|
+
node_free_(i);
|
|
1888
|
+
} else
|
|
1889
|
+
tape_allocator_.deallocate(nullptr, 0);
|
|
1850
1890
|
size_ = 0;
|
|
1851
1891
|
max_level_ = -1;
|
|
1852
1892
|
entry_id_ = 0u;
|
|
@@ -1861,11 +1901,11 @@ class index_gt {
|
|
|
1861
1901
|
clear();
|
|
1862
1902
|
|
|
1863
1903
|
if (nodes_)
|
|
1864
|
-
|
|
1904
|
+
nodes_allocator_t{}.deallocate(exchange(nodes_, nullptr), limits_.members);
|
|
1865
1905
|
if (contexts_) {
|
|
1866
1906
|
for (std::size_t i = 0; i != limits_.threads(); ++i)
|
|
1867
1907
|
contexts_[i].~context_t();
|
|
1868
|
-
|
|
1908
|
+
contexts_allocator_t{}.deallocate(exchange(contexts_, nullptr), limits_.threads());
|
|
1869
1909
|
}
|
|
1870
1910
|
limits_ = index_limits_t{0, 0};
|
|
1871
1911
|
capacity_ = 0;
|
|
@@ -1879,8 +1919,8 @@ class index_gt {
|
|
|
1879
1919
|
std::swap(config_, other.config_);
|
|
1880
1920
|
std::swap(limits_, other.limits_);
|
|
1881
1921
|
std::swap(metric_, other.metric_);
|
|
1882
|
-
std::swap(
|
|
1883
|
-
std::swap(
|
|
1922
|
+
std::swap(dynamic_allocator_, other.dynamic_allocator_);
|
|
1923
|
+
std::swap(tape_allocator_, other.tape_allocator_);
|
|
1884
1924
|
std::swap(pre_, other.pre_);
|
|
1885
1925
|
std::swap(viewed_file_, other.viewed_file_);
|
|
1886
1926
|
std::swap(max_level_, other.max_level_);
|
|
@@ -1912,13 +1952,13 @@ class index_gt {
|
|
|
1912
1952
|
if (!nodes_mutexes_.resize(limits.members))
|
|
1913
1953
|
return false;
|
|
1914
1954
|
|
|
1915
|
-
|
|
1955
|
+
nodes_allocator_t node_allocator;
|
|
1916
1956
|
node_t* new_nodes = node_allocator.allocate(limits.members);
|
|
1917
1957
|
if (!new_nodes)
|
|
1918
1958
|
return false;
|
|
1919
1959
|
|
|
1920
1960
|
std::size_t limits_threads = limits.threads();
|
|
1921
|
-
|
|
1961
|
+
contexts_allocator_t context_allocator;
|
|
1922
1962
|
context_t* new_contexts = context_allocator.allocate(limits_threads);
|
|
1923
1963
|
if (!new_contexts) {
|
|
1924
1964
|
node_allocator.deallocate(new_nodes, limits.members);
|
|
@@ -2408,6 +2448,10 @@ class index_gt {
|
|
|
2408
2448
|
template <typename progress_at = dummy_progress_t>
|
|
2409
2449
|
serialization_result_t load(char const* file_path, progress_at&& progress = {}) noexcept {
|
|
2410
2450
|
|
|
2451
|
+
// Remove previously stored objects
|
|
2452
|
+
reset();
|
|
2453
|
+
|
|
2454
|
+
// Start parsing the files
|
|
2411
2455
|
serialization_result_t result;
|
|
2412
2456
|
file_header_t state_buffer{};
|
|
2413
2457
|
std::FILE* file = std::fopen(file_path, "rb");
|
|
@@ -2493,6 +2537,11 @@ class index_gt {
|
|
|
2493
2537
|
*/
|
|
2494
2538
|
template <typename progress_at = dummy_progress_t>
|
|
2495
2539
|
serialization_result_t view(char const* file_path, progress_at&& progress = {}) noexcept {
|
|
2540
|
+
|
|
2541
|
+
// Remove previously stored objects
|
|
2542
|
+
reset();
|
|
2543
|
+
|
|
2544
|
+
// Start parsing the files
|
|
2496
2545
|
serialization_result_t result;
|
|
2497
2546
|
|
|
2498
2547
|
#if defined(USEARCH_DEFINED_WINDOWS)
|
|
@@ -2671,7 +2720,7 @@ class index_gt {
|
|
|
2671
2720
|
progress_at&& progress = progress_at{}) noexcept {
|
|
2672
2721
|
|
|
2673
2722
|
// Erase all the incoming links
|
|
2674
|
-
executor.execute_bulk(size(), [&](std::size_t
|
|
2723
|
+
executor.execute_bulk(size(), [&](std::size_t, std::size_t node_idx) {
|
|
2675
2724
|
node_t node = node_with_id_(node_idx);
|
|
2676
2725
|
for (level_t level = 0; level <= node.level(); ++level) {
|
|
2677
2726
|
neighbors_ref_t neighbors = neighbors_(node, level);
|
|
@@ -2708,7 +2757,7 @@ class index_gt {
|
|
|
2708
2757
|
|
|
2709
2758
|
using proposals_count_t = std::uint16_t;
|
|
2710
2759
|
using ids_allocator_t = typename allocator_traits_t::template rebind_alloc<id_t>;
|
|
2711
|
-
|
|
2760
|
+
dynamic_allocator_t& alloc = men.dynamic_allocator_;
|
|
2712
2761
|
|
|
2713
2762
|
// Create an atomic queue, as a ring structure, from/to which
|
|
2714
2763
|
// free men will be added/pulled.
|
|
@@ -2911,7 +2960,7 @@ class index_gt {
|
|
|
2911
2960
|
std::size_t node_bytes = node_bytes_(dims_to_store, level);
|
|
2912
2961
|
std::size_t non_vector_bytes = node_bytes - vector_bytes;
|
|
2913
2962
|
|
|
2914
|
-
byte_t* data = (byte_t*)
|
|
2963
|
+
byte_t* data = (byte_t*)tape_allocator_.allocate(node_bytes);
|
|
2915
2964
|
if (!data)
|
|
2916
2965
|
return node_bytes_split_t{};
|
|
2917
2966
|
return {{data, non_vector_bytes}, {data + non_vector_bytes, vector_bytes}};
|
|
@@ -2934,7 +2983,7 @@ class index_gt {
|
|
|
2934
2983
|
|
|
2935
2984
|
node_t node_make_copy_(node_bytes_split_t old_bytes) noexcept {
|
|
2936
2985
|
if (old_bytes.colocated()) {
|
|
2937
|
-
byte_t* data = (byte_t*)
|
|
2986
|
+
byte_t* data = (byte_t*)tape_allocator_.allocate(old_bytes.memory_usage());
|
|
2938
2987
|
std::memcpy(data, old_bytes.tape.data(), old_bytes.memory_usage());
|
|
2939
2988
|
return node_t{data, reinterpret_cast<scalar_t*>(data + old_bytes.tape.size())};
|
|
2940
2989
|
} else {
|
|
@@ -2953,7 +3002,7 @@ class index_gt {
|
|
|
2953
3002
|
|
|
2954
3003
|
node_t& node = nodes_[id];
|
|
2955
3004
|
std::size_t node_bytes = node_bytes_(node) - node_vector_bytes_(node) * !node_bytes_split_(node).colocated();
|
|
2956
|
-
|
|
3005
|
+
tape_allocator_.deallocate(node.tape(), node_bytes);
|
|
2957
3006
|
node = node_t{};
|
|
2958
3007
|
}
|
|
2959
3008
|
|