usearch 0.20.0 → 0.22.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 +46 -0
- package/.github/workflows/release.yml +28 -0
- package/.github/workflows/update_version.sh +2 -1
- package/CMakeLists.txt +4 -0
- package/Cargo.toml +1 -1
- package/README.md +93 -585
- package/c/lib.cpp +19 -15
- package/c/usearch.h +26 -23
- package/cmake/emscripten.cmake +8 -0
- package/conanfile.py +1 -1
- package/cpp/CMakeLists.txt +6 -2
- package/cpp/README.md +88 -0
- package/cpp/bench.cpp +72 -44
- package/cpp/test.cpp +13 -7
- package/docs/benchmarks.md +14 -12
- package/docs/golang/index.rst +6 -0
- package/docs/index.rst +5 -2
- package/docs/python/reference.rst +9 -2
- package/docs/rust/index.rst +6 -0
- package/docs/swift/index.rst +6 -0
- package/golang/README.md +28 -0
- package/include/usearch/index.hpp +668 -123
- package/include/usearch/index_punned_dense.hpp +305 -93
- package/include/usearch/index_punned_helpers.hpp +28 -3
- package/java/README.md +22 -0
- package/javascript/README.md +27 -0
- package/javascript/lib.cpp +1 -1
- package/package.json +1 -1
- package/python/README.md +283 -0
- package/python/lib.cpp +118 -16
- package/python/scripts/join.py +155 -0
- package/python/scripts/test.py +45 -241
- package/python/scripts/test_jit.py +244 -0
- package/python/usearch/eval.py +11 -3
- package/python/usearch/index.py +207 -26
- package/rust/README.md +67 -0
- package/swift/README.md +20 -0
- package/wasm/CMakeLists.txt +15 -0
- package/wasm/lib.cpp +5 -0
- package/wasmer.toml +8 -0
- package/python/usearch/join.py +0 -98
package/c/lib.cpp
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
#include <usearch/index_punned_dense.hpp>
|
|
4
4
|
|
|
5
|
+
#ifndef USEARCH_EXPORT
|
|
6
|
+
#define USEARCH_EXPORT
|
|
7
|
+
#endif
|
|
8
|
+
|
|
5
9
|
extern "C" {
|
|
6
10
|
#include "usearch.h"
|
|
7
11
|
}
|
|
@@ -88,7 +92,7 @@ index_punned_dense_metric_t udf(metric_kind_t kind, usearch_metric_t raw_ptr) {
|
|
|
88
92
|
|
|
89
93
|
extern "C" {
|
|
90
94
|
|
|
91
|
-
usearch_index_t usearch_init(usearch_init_options_t* options, usearch_error_t* error) {
|
|
95
|
+
USEARCH_EXPORT usearch_index_t usearch_init(usearch_init_options_t* options, usearch_error_t* error) {
|
|
92
96
|
|
|
93
97
|
assert(options && error);
|
|
94
98
|
|
|
@@ -108,48 +112,48 @@ usearch_index_t usearch_init(usearch_init_options_t* options, usearch_error_t* e
|
|
|
108
112
|
return result_ptr;
|
|
109
113
|
}
|
|
110
114
|
|
|
111
|
-
void usearch_free(usearch_index_t index, usearch_error_t*) { delete reinterpret_cast<index_t*>(index); }
|
|
115
|
+
USEARCH_EXPORT void usearch_free(usearch_index_t index, usearch_error_t*) { delete reinterpret_cast<index_t*>(index); }
|
|
112
116
|
|
|
113
|
-
void usearch_save(usearch_index_t index, char const* path, usearch_error_t* error) {
|
|
117
|
+
USEARCH_EXPORT void usearch_save(usearch_index_t index, char const* path, usearch_error_t* error) {
|
|
114
118
|
serialization_result_t result = reinterpret_cast<index_t*>(index)->save(path);
|
|
115
119
|
if (!result)
|
|
116
120
|
*error = result.error.what();
|
|
117
121
|
}
|
|
118
122
|
|
|
119
|
-
void usearch_load(usearch_index_t index, char const* path, usearch_error_t* error) {
|
|
123
|
+
USEARCH_EXPORT void usearch_load(usearch_index_t index, char const* path, usearch_error_t* error) {
|
|
120
124
|
serialization_result_t result = reinterpret_cast<index_t*>(index)->load(path);
|
|
121
125
|
if (!result)
|
|
122
126
|
*error = result.error.what();
|
|
123
127
|
}
|
|
124
128
|
|
|
125
|
-
void usearch_view(usearch_index_t index, char const* path, usearch_error_t* error) {
|
|
129
|
+
USEARCH_EXPORT void usearch_view(usearch_index_t index, char const* path, usearch_error_t* error) {
|
|
126
130
|
serialization_result_t result = reinterpret_cast<index_t*>(index)->view(path);
|
|
127
131
|
if (!result)
|
|
128
132
|
*error = result.error.what();
|
|
129
133
|
}
|
|
130
134
|
|
|
131
|
-
size_t usearch_size(usearch_index_t index, usearch_error_t*) { //
|
|
135
|
+
USEARCH_EXPORT size_t usearch_size(usearch_index_t index, usearch_error_t*) { //
|
|
132
136
|
return reinterpret_cast<index_t*>(index)->size();
|
|
133
137
|
}
|
|
134
138
|
|
|
135
|
-
size_t usearch_capacity(usearch_index_t index, usearch_error_t*) {
|
|
139
|
+
USEARCH_EXPORT size_t usearch_capacity(usearch_index_t index, usearch_error_t*) {
|
|
136
140
|
return reinterpret_cast<index_t*>(index)->capacity();
|
|
137
141
|
}
|
|
138
142
|
|
|
139
|
-
size_t usearch_dimensions(usearch_index_t index, usearch_error_t*) {
|
|
143
|
+
USEARCH_EXPORT size_t usearch_dimensions(usearch_index_t index, usearch_error_t*) {
|
|
140
144
|
return reinterpret_cast<index_t*>(index)->dimensions();
|
|
141
145
|
}
|
|
142
146
|
|
|
143
|
-
size_t usearch_connectivity(usearch_index_t index, usearch_error_t*) {
|
|
147
|
+
USEARCH_EXPORT size_t usearch_connectivity(usearch_index_t index, usearch_error_t*) {
|
|
144
148
|
return reinterpret_cast<index_t*>(index)->connectivity();
|
|
145
149
|
}
|
|
146
150
|
|
|
147
|
-
void usearch_reserve(usearch_index_t index, size_t capacity, usearch_error_t*) {
|
|
151
|
+
USEARCH_EXPORT void usearch_reserve(usearch_index_t index, size_t capacity, usearch_error_t*) {
|
|
148
152
|
// TODO: Consider returning the new capacity.
|
|
149
153
|
reinterpret_cast<index_t*>(index)->reserve(capacity);
|
|
150
154
|
}
|
|
151
155
|
|
|
152
|
-
void usearch_add(
|
|
156
|
+
USEARCH_EXPORT void usearch_add( //
|
|
153
157
|
usearch_index_t index, usearch_label_t label, void const* vector, usearch_scalar_kind_t kind, //
|
|
154
158
|
usearch_error_t* error) {
|
|
155
159
|
add_result_t result = add_(reinterpret_cast<index_t*>(index), label, vector, to_native_scalar(kind));
|
|
@@ -157,11 +161,11 @@ void usearch_add(
|
|
|
157
161
|
*error = result.error.what();
|
|
158
162
|
}
|
|
159
163
|
|
|
160
|
-
bool usearch_contains(usearch_index_t index, usearch_label_t label, usearch_error_t*) {
|
|
164
|
+
USEARCH_EXPORT bool usearch_contains(usearch_index_t index, usearch_label_t label, usearch_error_t*) {
|
|
161
165
|
return reinterpret_cast<index_t*>(index)->contains(label);
|
|
162
166
|
}
|
|
163
167
|
|
|
164
|
-
size_t usearch_search(
|
|
168
|
+
USEARCH_EXPORT size_t usearch_search( //
|
|
165
169
|
usearch_index_t index, void const* vector, usearch_scalar_kind_t kind, size_t results_limit, //
|
|
166
170
|
usearch_label_t* found_labels, usearch_distance_t* found_distances, usearch_error_t* error) {
|
|
167
171
|
search_result_t result = search_(reinterpret_cast<index_t*>(index), vector, to_native_scalar(kind), results_limit);
|
|
@@ -173,13 +177,13 @@ size_t usearch_search(
|
|
|
173
177
|
return result.dump_to(found_labels, found_distances);
|
|
174
178
|
}
|
|
175
179
|
|
|
176
|
-
bool usearch_get(
|
|
180
|
+
USEARCH_EXPORT bool usearch_get( //
|
|
177
181
|
usearch_index_t index, usearch_label_t label, //
|
|
178
182
|
void* vector, usearch_scalar_kind_t kind, usearch_error_t*) {
|
|
179
183
|
return get_(reinterpret_cast<index_t*>(index), label, vector, to_native_scalar(kind));
|
|
180
184
|
}
|
|
181
185
|
|
|
182
|
-
void usearch_remove(usearch_index_t, usearch_label_t, usearch_error_t* error) {
|
|
186
|
+
USEARCH_EXPORT void usearch_remove(usearch_index_t, usearch_label_t, usearch_error_t* error) {
|
|
183
187
|
if (error != nullptr)
|
|
184
188
|
*error = "USearch does not support removal of elements yet.";
|
|
185
189
|
}
|
package/c/usearch.h
CHANGED
|
@@ -4,17 +4,20 @@
|
|
|
4
4
|
#ifdef __cplusplus
|
|
5
5
|
extern "C" {
|
|
6
6
|
#endif
|
|
7
|
+
#ifndef USEARCH_EXPORT
|
|
8
|
+
#define USEARCH_EXPORT
|
|
9
|
+
#endif
|
|
7
10
|
#include <stdbool.h> // `bool`
|
|
8
11
|
#include <stdint.h> // `size_t`
|
|
9
12
|
|
|
10
|
-
typedef void* usearch_index_t;
|
|
11
|
-
typedef uint32_t usearch_label_t;
|
|
12
|
-
typedef float usearch_distance_t;
|
|
13
|
-
typedef char const* usearch_error_t;
|
|
13
|
+
USEARCH_EXPORT typedef void* usearch_index_t;
|
|
14
|
+
USEARCH_EXPORT typedef uint32_t usearch_label_t;
|
|
15
|
+
USEARCH_EXPORT typedef float usearch_distance_t;
|
|
16
|
+
USEARCH_EXPORT typedef char const* usearch_error_t;
|
|
14
17
|
|
|
15
|
-
typedef usearch_distance_t (*usearch_metric_t)(void const*, void const*);
|
|
18
|
+
USEARCH_EXPORT typedef usearch_distance_t (*usearch_metric_t)(void const*, void const*);
|
|
16
19
|
|
|
17
|
-
typedef enum usearch_metric_kind_t {
|
|
20
|
+
USEARCH_EXPORT typedef enum usearch_metric_kind_t {
|
|
18
21
|
usearch_metric_ip_k = 0,
|
|
19
22
|
usearch_metric_l2sq_k,
|
|
20
23
|
usearch_metric_cos_k,
|
|
@@ -27,7 +30,7 @@ typedef enum usearch_metric_kind_t {
|
|
|
27
30
|
usearch_metric_unknown_k,
|
|
28
31
|
} usearch_metric_kind_t;
|
|
29
32
|
|
|
30
|
-
typedef enum usearch_scalar_kind_t {
|
|
33
|
+
USEARCH_EXPORT typedef enum usearch_scalar_kind_t {
|
|
31
34
|
usearch_scalar_f32_k = 0,
|
|
32
35
|
usearch_scalar_f64_k,
|
|
33
36
|
usearch_scalar_f16_k,
|
|
@@ -36,7 +39,7 @@ typedef enum usearch_scalar_kind_t {
|
|
|
36
39
|
usearch_scalar_unknown_k,
|
|
37
40
|
} usearch_scalar_kind_t;
|
|
38
41
|
|
|
39
|
-
typedef struct usearch_init_options_t {
|
|
42
|
+
USEARCH_EXPORT typedef struct usearch_init_options_t {
|
|
40
43
|
|
|
41
44
|
usearch_metric_kind_t metric_kind;
|
|
42
45
|
usearch_metric_t metric;
|
|
@@ -48,39 +51,39 @@ typedef struct usearch_init_options_t {
|
|
|
48
51
|
size_t expansion_search;
|
|
49
52
|
} usearch_init_options_t;
|
|
50
53
|
|
|
51
|
-
usearch_index_t usearch_init(usearch_init_options_t*, usearch_error_t*);
|
|
52
|
-
void usearch_free(usearch_index_t, usearch_error_t*);
|
|
54
|
+
USEARCH_EXPORT usearch_index_t usearch_init(usearch_init_options_t*, usearch_error_t*);
|
|
55
|
+
USEARCH_EXPORT void usearch_free(usearch_index_t, usearch_error_t*);
|
|
53
56
|
|
|
54
|
-
void usearch_save(usearch_index_t, char const* path, usearch_error_t*);
|
|
55
|
-
void usearch_load(usearch_index_t, char const* path, usearch_error_t*);
|
|
56
|
-
void usearch_view(usearch_index_t, char const* path, usearch_error_t*);
|
|
57
|
+
USEARCH_EXPORT void usearch_save(usearch_index_t, char const* path, usearch_error_t*);
|
|
58
|
+
USEARCH_EXPORT void usearch_load(usearch_index_t, char const* path, usearch_error_t*);
|
|
59
|
+
USEARCH_EXPORT void usearch_view(usearch_index_t, char const* path, usearch_error_t*);
|
|
57
60
|
|
|
58
|
-
size_t usearch_size(usearch_index_t, usearch_error_t*);
|
|
59
|
-
size_t usearch_capacity(usearch_index_t, usearch_error_t*);
|
|
60
|
-
size_t usearch_dimensions(usearch_index_t, usearch_error_t*);
|
|
61
|
-
size_t usearch_connectivity(usearch_index_t, usearch_error_t*);
|
|
61
|
+
USEARCH_EXPORT size_t usearch_size(usearch_index_t, usearch_error_t*);
|
|
62
|
+
USEARCH_EXPORT size_t usearch_capacity(usearch_index_t, usearch_error_t*);
|
|
63
|
+
USEARCH_EXPORT size_t usearch_dimensions(usearch_index_t, usearch_error_t*);
|
|
64
|
+
USEARCH_EXPORT size_t usearch_connectivity(usearch_index_t, usearch_error_t*);
|
|
62
65
|
|
|
63
|
-
void usearch_reserve(usearch_index_t, size_t capacity, usearch_error_t*);
|
|
66
|
+
USEARCH_EXPORT void usearch_reserve(usearch_index_t, size_t capacity, usearch_error_t*);
|
|
64
67
|
|
|
65
|
-
void usearch_add(
|
|
68
|
+
USEARCH_EXPORT void usearch_add( //
|
|
66
69
|
usearch_index_t, usearch_label_t, void const* vector, usearch_scalar_kind_t vector_kind, //
|
|
67
70
|
usearch_error_t*);
|
|
68
71
|
|
|
69
|
-
bool usearch_contains(usearch_index_t, usearch_label_t, usearch_error_t*);
|
|
72
|
+
USEARCH_EXPORT bool usearch_contains(usearch_index_t, usearch_label_t, usearch_error_t*);
|
|
70
73
|
|
|
71
74
|
/**
|
|
72
75
|
* @brief Performs k-Approximate Nearest Neighbors Search.
|
|
73
76
|
* @return Number of found matches.
|
|
74
77
|
*/
|
|
75
|
-
size_t usearch_search(
|
|
78
|
+
USEARCH_EXPORT size_t usearch_search( //
|
|
76
79
|
usearch_index_t, void const* query_vector, usearch_scalar_kind_t query_kind, size_t results_limit, //
|
|
77
80
|
usearch_label_t* found_labels, usearch_distance_t* found_distances, usearch_error_t*);
|
|
78
81
|
|
|
79
|
-
bool usearch_get(
|
|
82
|
+
USEARCH_EXPORT bool usearch_get( //
|
|
80
83
|
usearch_index_t, usearch_label_t, //
|
|
81
84
|
void* vector, usearch_scalar_kind_t vector_kind, usearch_error_t*);
|
|
82
85
|
|
|
83
|
-
void usearch_remove(usearch_index_t, usearch_label_t, usearch_error_t*);
|
|
86
|
+
USEARCH_EXPORT void usearch_remove(usearch_index_t, usearch_label_t, usearch_error_t*);
|
|
84
87
|
|
|
85
88
|
#ifdef __cplusplus
|
|
86
89
|
}
|
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.
|
|
7
|
+
version = '0.22.0'
|
|
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/CMakeLists.txt
CHANGED
|
@@ -13,7 +13,9 @@ endif()
|
|
|
13
13
|
# https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_COMPILER_ID.html
|
|
14
14
|
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
|
15
15
|
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
|
|
16
|
-
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -
|
|
16
|
+
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g")
|
|
17
|
+
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -O3 -g")
|
|
18
|
+
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fsanitize=leak -fsanitize=alignment -fsanitize=undefined")
|
|
17
19
|
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -Wextra -Wno-conversion -Wno-unknown-pragmas")
|
|
18
20
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native")
|
|
19
21
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fmax-errors=1")
|
|
@@ -22,7 +24,9 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
|
|
22
24
|
|
|
23
25
|
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
|
24
26
|
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
|
|
25
|
-
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -
|
|
27
|
+
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g")
|
|
28
|
+
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -O3 -g")
|
|
29
|
+
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fsanitize=leak -fsanitize=alignment -fsanitize=undefined")
|
|
26
30
|
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wfatal-errors")
|
|
27
31
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp")
|
|
28
32
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic")
|
package/cpp/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# USearch C++ Inteface
|
|
2
|
+
|
|
3
|
+
## Installation
|
|
4
|
+
|
|
5
|
+
To use in a C++ project simply copy the `include/usearch/index.hpp` header into your project.
|
|
6
|
+
Alternatively fetch it with CMake:
|
|
7
|
+
|
|
8
|
+
```cmake
|
|
9
|
+
FetchContent_Declare(usearch GIT_REPOSITORY https://github.com/unum-cloud/usearch.git)
|
|
10
|
+
FetchContent_MakeAvailable(usearch)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quickstart
|
|
14
|
+
|
|
15
|
+
Once included, the low-level C++11 interface is as simple as it gets: `reserve()`, `add()`, `search()`, `size()`, `capacity()`, `save()`, `load()`, `view()`.
|
|
16
|
+
This covers 90% of use-cases.
|
|
17
|
+
|
|
18
|
+
```c++
|
|
19
|
+
using namespace unum::usearch;
|
|
20
|
+
|
|
21
|
+
index_gt<cos_gt<float>> index;
|
|
22
|
+
float vec[3] = {0.1, 0.3, 0.2};
|
|
23
|
+
|
|
24
|
+
index.reserve(10);
|
|
25
|
+
index.add(/* label: */ 42, /* vector: */ {&vec[0], 3});
|
|
26
|
+
auto results = index.search(/* query: */ {&vec[0], 3}, 5 /* neighbors */);
|
|
27
|
+
|
|
28
|
+
for (std::size_t i = 0; i != results.size(); ++i)
|
|
29
|
+
results[i].element.label, results[i].element.vector, results[i].distance;
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The `add` is thread-safe for concurrent index construction.
|
|
33
|
+
|
|
34
|
+
## Serialization
|
|
35
|
+
|
|
36
|
+
```c++
|
|
37
|
+
index.save("index.usearch");
|
|
38
|
+
index.load("index.usearch"); // Copying from disk
|
|
39
|
+
index.view("index.usearch"); // Memory-mapping from disk
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## User-Defined Metrics in C++
|
|
43
|
+
|
|
44
|
+
For advanced users, more compile-time abstractions are available.
|
|
45
|
+
|
|
46
|
+
```cpp
|
|
47
|
+
template <typename metric_at = ip_gt<float>, //
|
|
48
|
+
typename label_at = std::size_t, // `uint32_t`, `uuid_t`...
|
|
49
|
+
typename id_at = std::uint32_t, // `uint40_t`, `uint64_t`...
|
|
50
|
+
typename scalar_at = float, // `double`, `half`, `char`...
|
|
51
|
+
typename allocator_at = std::allocator<char>> //
|
|
52
|
+
class index_gt;
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
You may want to use a custom memory allocator or a rare scalar type, but most often, you would start by defining a custom similarity measure.
|
|
56
|
+
The function object should have the following signature to support different-length vectors.
|
|
57
|
+
|
|
58
|
+
```cpp
|
|
59
|
+
struct custom_metric_t {
|
|
60
|
+
T operator()(T const* a, T const* b, std::size_t a_length, std::size_t b_length) const;
|
|
61
|
+
};
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The following distances are pre-packaged:
|
|
65
|
+
|
|
66
|
+
- `cos_gt<scalar_t>` for "Cosine" or "Angular" distance.
|
|
67
|
+
- `ip_gt<scalar_t>` for "Inner Product" or "Dot Product" distance.
|
|
68
|
+
- `l2sq_gt<scalar_t>` for the squared "L2" or "Euclidean" distance.
|
|
69
|
+
- `jaccard_gt<scalar_t>` for "Jaccard" distance between two ordered sets of unique elements.
|
|
70
|
+
- `hamming_gt<scalar_t>` for "Hamming" distance, as the number of shared bits in hashes.
|
|
71
|
+
- `tanimoto_gt<scalar_t>` for "Tanimoto" coefficient for bit-strings.
|
|
72
|
+
- `sorensen_gt<scalar_t>` for "Dice-Sorensen" coefficient for bit-strings.
|
|
73
|
+
- `pearson_correlation_gt<scalar_t>` for "Pearson" correlation between probability distributions.
|
|
74
|
+
- `haversine_gt<scalar_t>` for "Haversine" or "Great Circle" distance between coordinates used in GIS applications.
|
|
75
|
+
|
|
76
|
+
## Multi-Threading
|
|
77
|
+
|
|
78
|
+
Most AI, HPC, or Big Data packages use some form of a thread pool.
|
|
79
|
+
Instead of spawning additional threads within USearch, we focus on the thread safety of `add()` function, simplifying resource management.
|
|
80
|
+
|
|
81
|
+
```cpp
|
|
82
|
+
#pragma omp parallel for
|
|
83
|
+
for (std::size_t i = 0; i < n; ++i)
|
|
84
|
+
native.add(label, span_t{vector, dims}, add_config_t { .thread = omp_get_thread_num() });
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
During initialization, we allocate enough temporary memory for all the cores on the machine.
|
|
88
|
+
On the call, the user can supply the identifier of the current thread, making this library easy to integrate with OpenMP and similar tools.
|
package/cpp/bench.cpp
CHANGED
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
|
|
16
16
|
#define STDERR_FILENO HANDLE(2)
|
|
17
17
|
#else
|
|
18
|
-
#
|
|
18
|
+
#if defined(__linux__)
|
|
19
|
+
#include <execinfo.h> // `backtrace`
|
|
20
|
+
#endif
|
|
19
21
|
#include <fcntl.h> // `open`
|
|
20
22
|
#include <stdlib.h> // `getenv`
|
|
21
23
|
#include <sys/mman.h> // `mmap`
|
|
@@ -24,6 +26,7 @@
|
|
|
24
26
|
|
|
25
27
|
#include <sys/stat.h> // `stat`
|
|
26
28
|
|
|
29
|
+
#include <algorithm>
|
|
27
30
|
#include <csignal>
|
|
28
31
|
#include <cstdio>
|
|
29
32
|
#include <iostream> // `std::cerr`
|
|
@@ -32,9 +35,12 @@
|
|
|
32
35
|
#include <string> // `std::to_string`
|
|
33
36
|
#include <thread> // `std::thread::hardware_concurrency()`
|
|
34
37
|
#include <variant> // `std::monostate`
|
|
38
|
+
#include <vector>
|
|
35
39
|
|
|
36
40
|
#include <clipp.h> // Command Line Interface
|
|
37
|
-
#
|
|
41
|
+
#if USEARCH_USE_OPENMP
|
|
42
|
+
#include <omp.h> // `omp_set_num_threads()`
|
|
43
|
+
#endif
|
|
38
44
|
|
|
39
45
|
#include <simsimd/simsimd.h>
|
|
40
46
|
|
|
@@ -43,6 +49,7 @@
|
|
|
43
49
|
using namespace unum::usearch;
|
|
44
50
|
using namespace unum;
|
|
45
51
|
|
|
52
|
+
using label_t = std::int64_t;
|
|
46
53
|
using vector_id_t = std::uint32_t;
|
|
47
54
|
using vector_view_t = span_gt<float const>;
|
|
48
55
|
|
|
@@ -243,10 +250,10 @@ struct running_stats_printer_t {
|
|
|
243
250
|
std::size_t new_progress = progress.load();
|
|
244
251
|
if (new_progress - last_printed_progress < step)
|
|
245
252
|
return;
|
|
246
|
-
print(new_progress);
|
|
253
|
+
print(new_progress, total);
|
|
247
254
|
}
|
|
248
255
|
|
|
249
|
-
void print(std::size_t progress) {
|
|
256
|
+
void print(std::size_t progress, std::size_t total) {
|
|
250
257
|
|
|
251
258
|
constexpr char bars_k[] = "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||";
|
|
252
259
|
constexpr std::size_t bars_len_k = 60;
|
|
@@ -267,6 +274,7 @@ struct running_stats_printer_t {
|
|
|
267
274
|
|
|
268
275
|
last_printed_progress = progress;
|
|
269
276
|
last_printed_time = time_new;
|
|
277
|
+
this->total = total;
|
|
270
278
|
}
|
|
271
279
|
};
|
|
272
280
|
|
|
@@ -275,15 +283,19 @@ void index_many(index_at& native, std::size_t n, vector_id_at const* ids, real_a
|
|
|
275
283
|
|
|
276
284
|
running_stats_printer_t printer{n, "Indexing"};
|
|
277
285
|
|
|
286
|
+
#if USEARCH_USE_OPENMP
|
|
278
287
|
#pragma omp parallel for schedule(static, 32)
|
|
288
|
+
#endif
|
|
279
289
|
for (std::size_t i = 0; i < n; ++i) {
|
|
280
290
|
add_config_t config;
|
|
291
|
+
#if USEARCH_USE_OPENMP
|
|
281
292
|
config.thread = omp_get_thread_num();
|
|
293
|
+
#endif
|
|
282
294
|
config.store_vector = true;
|
|
283
295
|
vector_view_t vector{vectors + dims * i, dims};
|
|
284
296
|
native.add(ids[i], vector, config);
|
|
285
297
|
printer.progress++;
|
|
286
|
-
if (
|
|
298
|
+
if (config.thread == 0)
|
|
287
299
|
printer.refresh();
|
|
288
300
|
}
|
|
289
301
|
}
|
|
@@ -296,34 +308,18 @@ void search_many( //
|
|
|
296
308
|
std::string name = "Search " + std::to_string(wanted);
|
|
297
309
|
running_stats_printer_t printer{n, name.c_str()};
|
|
298
310
|
|
|
311
|
+
#if USEARCH_USE_OPENMP
|
|
299
312
|
#pragma omp parallel for schedule(static, 32)
|
|
313
|
+
#endif
|
|
300
314
|
for (std::size_t i = 0; i < n; ++i) {
|
|
301
315
|
search_config_t config;
|
|
316
|
+
#if USEARCH_USE_OPENMP
|
|
302
317
|
config.thread = omp_get_thread_num();
|
|
318
|
+
#endif
|
|
303
319
|
vector_view_t vector{vectors + dims * i, dims};
|
|
304
320
|
native.search(vector, wanted, config).dump_to(ids + wanted * i, distances + wanted * i);
|
|
305
321
|
printer.progress++;
|
|
306
|
-
if (
|
|
307
|
-
printer.refresh();
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
template <typename index_at, typename vector_id_at, typename real_at>
|
|
312
|
-
void paginate_many( //
|
|
313
|
-
index_at& native, std::size_t n, real_at const* vectors, std::size_t dims, std::size_t wanted,
|
|
314
|
-
vector_id_at const* hints) {
|
|
315
|
-
|
|
316
|
-
std::string name = "Paginate " + std::to_string(wanted);
|
|
317
|
-
running_stats_printer_t printer{n, name.c_str()};
|
|
318
|
-
|
|
319
|
-
#pragma omp parallel for schedule(static, 32)
|
|
320
|
-
for (std::size_t i = 0; i < n; ++i) {
|
|
321
|
-
search_config_t config;
|
|
322
|
-
config.thread = omp_get_thread_num();
|
|
323
|
-
vector_view_t vector{vectors + dims * i, dims};
|
|
324
|
-
native.search_around(hints[i], vector, wanted, config);
|
|
325
|
-
printer.progress++;
|
|
326
|
-
if (omp_get_thread_num() == 0)
|
|
322
|
+
if (config.thread == 0)
|
|
327
323
|
printer.refresh();
|
|
328
324
|
}
|
|
329
325
|
}
|
|
@@ -332,6 +328,8 @@ template <typename dataset_at, typename index_at> //
|
|
|
332
328
|
static void single_shot(dataset_at& dataset, index_at& index, bool construct = true) {
|
|
333
329
|
using label_t = typename index_at::label_t;
|
|
334
330
|
using distance_t = typename index_at::distance_t;
|
|
331
|
+
using join_result_t = typename index_at::join_result_t;
|
|
332
|
+
constexpr std::size_t missing_label = std::numeric_limits<label_t>::max();
|
|
335
333
|
|
|
336
334
|
std::printf("\n");
|
|
337
335
|
std::printf("------------\n");
|
|
@@ -348,7 +346,7 @@ static void single_shot(dataset_at& dataset, index_at& index, bool construct = t
|
|
|
348
346
|
search_many(index, dataset.queries_count(), dataset.query(0), dataset.dimensions(), dataset.neighborhood_size(),
|
|
349
347
|
found_neighbors.data(), found_distances.data());
|
|
350
348
|
|
|
351
|
-
// Evaluate quality
|
|
349
|
+
// Evaluate search quality
|
|
352
350
|
std::size_t recall_at_1 = 0, recall_full = 0;
|
|
353
351
|
for (std::size_t i = 0; i != dataset.queries_count(); ++i) {
|
|
354
352
|
auto expected = dataset.neighborhood(i);
|
|
@@ -360,13 +358,37 @@ static void single_shot(dataset_at& dataset, index_at& index, bool construct = t
|
|
|
360
358
|
std::printf("Recall@1 %.2f %%\n", recall_at_1 * 100.f / dataset.queries_count());
|
|
361
359
|
std::printf("Recall %.2f %%\n", recall_full * 100.f / dataset.queries_count());
|
|
362
360
|
|
|
363
|
-
//
|
|
364
|
-
std::vector<
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
361
|
+
// Perform joins
|
|
362
|
+
std::vector<label_t> man_to_woman(dataset.vectors_count());
|
|
363
|
+
std::vector<label_t> woman_to_man(dataset.vectors_count());
|
|
364
|
+
std::size_t join_attempts = 0;
|
|
365
|
+
{
|
|
366
|
+
index_at& men = index;
|
|
367
|
+
index_at women = index.copy().index;
|
|
368
|
+
std::fill(man_to_woman.begin(), man_to_woman.end(), missing_label);
|
|
369
|
+
std::fill(woman_to_man.begin(), woman_to_man.end(), missing_label);
|
|
370
|
+
{
|
|
371
|
+
executor_default_t executor(index.limits().threads());
|
|
372
|
+
running_stats_printer_t printer{1, "Join"};
|
|
373
|
+
join_result_t result = index_at::join( //
|
|
374
|
+
men, women, join_config_t{executor.size()}, //
|
|
375
|
+
man_to_woman.data(), woman_to_man.data(), //
|
|
376
|
+
executor, [&](std::size_t progress, std::size_t total) {
|
|
377
|
+
if (progress % 1000 == 0)
|
|
378
|
+
printer.print(progress, total);
|
|
379
|
+
});
|
|
380
|
+
join_attempts = result.cycles;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// Evaluate join quality
|
|
384
|
+
std::size_t recall_join = 0, unmatched_count = 0;
|
|
385
|
+
for (std::size_t i = 0; i != index.size(); ++i) {
|
|
386
|
+
recall_join += man_to_woman[i] == i;
|
|
387
|
+
unmatched_count += man_to_woman[i] == missing_label;
|
|
388
|
+
}
|
|
389
|
+
std::printf("Recall Joins %.2f %%\n", recall_join * 100.f / index.size());
|
|
390
|
+
std::printf("Unmatched %.2f %% (%zu items)\n", unmatched_count * 100.f / index.size(), unmatched_count);
|
|
391
|
+
std::printf("Proposals %.2f / man (%zu total)\n", join_attempts * 1.f / index.size(), join_attempts);
|
|
370
392
|
|
|
371
393
|
std::printf("------------\n");
|
|
372
394
|
std::printf("\n");
|
|
@@ -379,7 +401,7 @@ void handler(int sig) {
|
|
|
379
401
|
// get void*'s for all entries on the stack
|
|
380
402
|
#if defined(USEARCH_DEFINED_WINDOWS)
|
|
381
403
|
size = CaptureStackBackTrace(0, 10, array, NULL);
|
|
382
|
-
#
|
|
404
|
+
#elif defined(USEARCH_DEFINED_LINUX)
|
|
383
405
|
size = backtrace(array, 10);
|
|
384
406
|
#endif // WINDOWS
|
|
385
407
|
|
|
@@ -401,7 +423,7 @@ void handler(int sig) {
|
|
|
401
423
|
WriteFile(STDERR_FILENO, "\n", 1, &bytes_written, NULL);
|
|
402
424
|
}
|
|
403
425
|
free(symbol);
|
|
404
|
-
#
|
|
426
|
+
#elif defined(USEARCH_DEFINED_LINUX)
|
|
405
427
|
backtrace_symbols_fd(array, size, STDERR_FILENO);
|
|
406
428
|
#endif // WINDOWS
|
|
407
429
|
|
|
@@ -491,7 +513,7 @@ void run_punned(dataset_at& dataset, args_t const& args, index_config_t config,
|
|
|
491
513
|
|
|
492
514
|
std::printf("Will benchmark an on-disk view\n");
|
|
493
515
|
|
|
494
|
-
index_at index_view = index.fork();
|
|
516
|
+
index_at index_view = index.fork().index;
|
|
495
517
|
index_view.view(args.path_output.c_str());
|
|
496
518
|
single_shot(dataset, index_view, false);
|
|
497
519
|
}
|
|
@@ -518,24 +540,24 @@ void run_big_or_small(dataset_at& dataset, args_t const& args, index_config_t co
|
|
|
518
540
|
if (args.native) {
|
|
519
541
|
if (args.metric_cos) {
|
|
520
542
|
std::printf("-- Metric: Angular\n");
|
|
521
|
-
run_typed<index_gt<cos_gt<float>,
|
|
543
|
+
run_typed<index_gt<cos_gt<float>, label_t, neighbor_id_at>>(dataset, args, config, limits);
|
|
522
544
|
} else if (args.metric_l2) {
|
|
523
545
|
std::printf("-- Metric: Euclidean\n");
|
|
524
|
-
run_typed<index_gt<l2sq_gt<float>,
|
|
546
|
+
run_typed<index_gt<l2sq_gt<float>, label_t, neighbor_id_at>>(dataset, args, config, limits);
|
|
525
547
|
} else if (args.metric_haversine) {
|
|
526
548
|
std::printf("-- Metric: Haversine\n");
|
|
527
|
-
run_typed<index_gt<haversine_gt<float>,
|
|
549
|
+
run_typed<index_gt<haversine_gt<float>, label_t, neighbor_id_at>>(dataset, args, config, limits);
|
|
528
550
|
} else {
|
|
529
551
|
std::printf("-- Metric: Inner Product\n");
|
|
530
|
-
run_typed<index_gt<ip_gt<float>,
|
|
552
|
+
run_typed<index_gt<ip_gt<float>, label_t, neighbor_id_at>>(dataset, args, config, limits);
|
|
531
553
|
}
|
|
532
554
|
} else
|
|
533
|
-
run_punned<index_punned_dense_gt<
|
|
555
|
+
run_punned<index_punned_dense_gt<label_t, neighbor_id_at>>(dataset, args, config, limits);
|
|
534
556
|
}
|
|
535
557
|
|
|
536
558
|
void report_alternative_setups() {
|
|
537
559
|
using set_member_t = std::uint32_t;
|
|
538
|
-
using sets_index_t = index_gt<jaccard_gt<set_member_t>,
|
|
560
|
+
using sets_index_t = index_gt<jaccard_gt<set_member_t>, label_t, std::uint32_t>;
|
|
539
561
|
set_member_t set_a[] = {10, 12, 15};
|
|
540
562
|
set_member_t set_b[] = {11, 12, 15, 16};
|
|
541
563
|
sets_index_t index;
|
|
@@ -625,12 +647,14 @@ int main(int argc, char** argv) {
|
|
|
625
647
|
exit(0);
|
|
626
648
|
}
|
|
627
649
|
|
|
650
|
+
#if USEARCH_USE_OPENMP
|
|
628
651
|
// Instead of relying on `multithreaded` from "index_punned_dense.hpp" we will use OpenMP
|
|
629
652
|
// to better estimate statistics between tasks batches, without having to recreate
|
|
630
653
|
// the threads.
|
|
631
654
|
omp_set_dynamic(true);
|
|
632
655
|
omp_set_num_threads(args.threads);
|
|
633
656
|
std::printf("- OpenMP threads: %d\n", omp_get_max_threads());
|
|
657
|
+
#endif
|
|
634
658
|
|
|
635
659
|
std::printf("- Dataset: \n");
|
|
636
660
|
std::printf("-- Base vectors path: %s\n", args.path_vectors.c_str());
|
|
@@ -658,7 +682,7 @@ int main(int argc, char** argv) {
|
|
|
658
682
|
// config.expansion_search = args.expansion_search;
|
|
659
683
|
index_limits_t limits;
|
|
660
684
|
limits.threads_add = limits.threads_search = args.threads;
|
|
661
|
-
limits.
|
|
685
|
+
limits.members = dataset.vectors_count();
|
|
662
686
|
|
|
663
687
|
std::printf("- Index: \n");
|
|
664
688
|
std::printf("-- Connectivity: %zu\n", config.connectivity);
|
|
@@ -666,7 +690,11 @@ int main(int argc, char** argv) {
|
|
|
666
690
|
// std::printf("-- Expansion @ Search: %zu\n", config.expansion_search);
|
|
667
691
|
|
|
668
692
|
if (args.big)
|
|
693
|
+
#ifdef USEARCH_64BIT_ENV
|
|
669
694
|
run_big_or_small<uint40_t>(dataset, args, config, limits);
|
|
695
|
+
#else
|
|
696
|
+
std::printf("Error: Don't use 40 bit identifiers in 32bit environment\n");
|
|
697
|
+
#endif
|
|
670
698
|
else
|
|
671
699
|
run_big_or_small<std::uint32_t>(dataset, args, config, limits);
|
|
672
700
|
|
package/cpp/test.cpp
CHANGED
|
@@ -84,17 +84,23 @@ template <typename scalar_at, typename index_at> void test3d_punned(index_at&& i
|
|
|
84
84
|
using view_t = span_gt<scalar_t const>;
|
|
85
85
|
using span_t = span_gt<scalar_at>;
|
|
86
86
|
|
|
87
|
-
scalar_t
|
|
87
|
+
scalar_t vec42[3] = {10, 20, 15};
|
|
88
|
+
scalar_t vec43[3] = {19, 22, 11};
|
|
88
89
|
|
|
89
90
|
index.reserve(10);
|
|
90
|
-
index.add(42, view_t{&
|
|
91
|
+
index.add(42, view_t{&vec42[0], 3ul});
|
|
91
92
|
|
|
92
93
|
// Reconstruct
|
|
93
|
-
scalar_t
|
|
94
|
-
index.get(42, span_t{&
|
|
95
|
-
expect(
|
|
96
|
-
expect(
|
|
97
|
-
expect(
|
|
94
|
+
scalar_t vec42_reconstructed[3] = {0, 0, 0};
|
|
95
|
+
index.get(42, span_t{&vec42_reconstructed[0], 3ul});
|
|
96
|
+
expect(vec42_reconstructed[0] == vec42[0]);
|
|
97
|
+
expect(vec42_reconstructed[1] == vec42[1]);
|
|
98
|
+
expect(vec42_reconstructed[2] == vec42[2]);
|
|
99
|
+
|
|
100
|
+
index.add(43, view_t{&vec43[0], 3ul});
|
|
101
|
+
expect(index.size() == 2);
|
|
102
|
+
index.remove(43);
|
|
103
|
+
expect(index.size() == 1);
|
|
98
104
|
}
|
|
99
105
|
|
|
100
106
|
template <typename index_at> void test_sets(index_at&& index) {
|