usearch 1.2.1 → 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.
@@ -17,6 +17,32 @@ permissions:
17
17
 
18
18
  jobs:
19
19
 
20
+ test_c:
21
+ name: Test C
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - uses: actions/checkout@v3
25
+ with:
26
+ ref: main-dev
27
+ - run: git submodule update --init --recursive
28
+ - name: Prepare Environment
29
+ run: |
30
+ sudo apt update &&
31
+ sudo apt install -y cmake g++-12 build-essential libjemalloc-dev
32
+ - name: Build
33
+ run: |
34
+ cmake -B ./build_release \
35
+ -DCMAKE_CXX_COMPILER="g++-12" \
36
+ -DCMAKE_BUILD_TYPE=Release \
37
+ -DUSEARCH_BUILD_TEST=1 \
38
+ -DUSEARCH_USE_OPENMP=1 \
39
+ -DUSEARCH_USE_SIMSIMD=1 \
40
+ -DUSEARCH_USE_JEMALLOC=1 \
41
+ -DUSEARCH_BUILD_BENCHMARK=0 &&
42
+ make -C ./build_release -j
43
+ - name: Run tests
44
+ run: ./build_release/test_c
45
+
20
46
  test_cpp:
21
47
  name: Test C++
22
48
  runs-on: ubuntu-latest
@@ -287,7 +287,7 @@ jobs:
287
287
  run: |
288
288
  sudo apt update &&
289
289
  sudo apt install -y doxygen graphviz dia git &&
290
- pip install sphinx==7.1.2 sphinx-js breathe furo m2r2 sphinxcontrib-googleanalytics==0.2.dev20220708 sphinxcontrib-jquery &&
290
+ pip install sphinx==5.3.0 sphinx-js==3.2.1 breathe==4.35.0 furo==2023.3.27 m2r2==0.3.3.post2 sphinxcontrib-googleanalytics==0.2.dev20220708 sphinxcontrib-jquery==4.1 &&
291
291
  npm install -g jsdoc
292
292
  - name: Install USearch from PyPi
293
293
  run: pip install usearch
package/CMakeLists.txt CHANGED
@@ -113,6 +113,7 @@ if(USEARCH_INSTALL)
113
113
  endif()
114
114
 
115
115
  if(${USEARCH_BUILD_TEST} OR ${USEARCH_BUILD_BENCHMARK})
116
+ add_subdirectory(c)
116
117
  add_subdirectory(cpp)
117
118
  endif()
118
119
 
package/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "usearch"
3
- version = "1.2.1"
3
+ version = "1.3.0"
4
4
  authors = ["Ash Vardanian <1983160+ashvardanian@users.noreply.github.com>"]
5
5
  description = "Smaller & Faster Single-File Vector Search Engine from Unum"
6
6
  edition = "2021"
package/README.md CHANGED
@@ -109,10 +109,10 @@ Comparing the performance of FAISS against USearch on 1 Million 96-dimensional v
109
109
 
110
110
  | | FAISS, `f32` | USearch, `f32` | USearch, `f16` | USearch, `i8` |
111
111
  | :----------- | -----------: | -------------: | -------------: | ----------------: |
112
- | Batch Insert | 16 K/s | 73 K/s | 100 K/s | 104 K/s **+550%** |
113
- | Batch Search | 82 K/s | 103 K/s | 113 K/s | 134 K/s **+63%** |
114
- | Bulk Insert | 76 K/s | 105 K/s | 115 K/s | 202 K/s **+165%** |
115
- | Bulk Search | 118 K/s | 174 K/s | 173 K/s | 304 K/s **+157%** |
112
+ | Batch Insert | 16 K/s | 73 K/s | 100 K/s | 104 K/s __+550%__ |
113
+ | Batch Search | 82 K/s | 103 K/s | 113 K/s | 134 K/s __+63%__ |
114
+ | Bulk Insert | 76 K/s | 105 K/s | 115 K/s | 202 K/s __+165%__ |
115
+ | Bulk Search | 118 K/s | 174 K/s | 173 K/s | 304 K/s __+157%__ |
116
116
  | Recall @ 10 | 99% | 99.2% | 99.1% | 99.2% |
117
117
 
118
118
  > HNSW was configured with identical hyper-parameters:
@@ -152,10 +152,16 @@ Instead, we have focused on high-precision arithmetic over low-precision downcas
152
152
  The same index, and `add` and `search` operations will automatically down-cast or up-cast between `f32_t`, `f16_t`, `f64_t`, and `i8_t` representations, even if the hardware doesn't natively support it.
153
153
  Continuing the topic of memory efficiency, we provide a `uint40_t` to allow collection with over 4B+ vectors without allocating 8 bytes for every neighbor reference in the proximity graph.
154
154
 
155
- ## Serving `Index` from Disk
155
+ ## Serialization & Serving `Index` from Disk
156
156
 
157
- With USearch, you can serve indexes from external memory, enabling you to optimize your server choices for indexing speed and serving costs.
158
- This can result in **20x cost reduction** on AWS and other public clouds.
157
+ USearch supports multiple forms of serialization:
158
+
159
+ - Into a __file__ defined with a path.
160
+ - Into a __stream__ defined with a callback, serializing or reconstructing incrementally.
161
+ - Into a __buffer__ of fixed length, or a memory-mapped file, that supports random access.
162
+
163
+ The latter allows you to serve indexes from external memory, enabling you to optimize your server choices for indexing speed and serving costs.
164
+ This can result in __20x cost reduction__ on AWS and other public clouds.
159
165
 
160
166
  ```py
161
167
  index.save("index.usearch")
@@ -186,10 +192,10 @@ many_in_many: BatchMatches = search(vectors, vectors, 50, MetricKind.L2sq, exact
186
192
  ```
187
193
 
188
194
  By passing the `exact=True` argument, the system bypasses indexing altogether and performs a brute-force search through the entire dataset using SIMD-optimized similarity metrics from [SimSIMD](https://github.com/ashvardanian/simsimd).
189
- When compared to FAISS's `IndexFlatL2` in Google Colab, **[USearch may offer up to a 20x performance improvement](https://github.com/unum-cloud/usearch/issues/176#issuecomment-1666650778)**:
195
+ When compared to FAISS's `IndexFlatL2` in Google Colab, __[USearch may offer up to a 20x performance improvement](https://github.com/unum-cloud/usearch/issues/176#issuecomment-1666650778)__:
190
196
 
191
- - `faiss.IndexFlatL2`: **55.3 ms**.
192
- - `usearch.index.search`: **2.54 ms**.
197
+ - `faiss.IndexFlatL2`: __55.3 ms__.
198
+ - `usearch.index.search`: __2.54 ms__.
193
199
 
194
200
  ## `Indexes` for Multi-Index Lookups
195
201
 
package/c/lib.cpp CHANGED
@@ -54,7 +54,7 @@ add_result_t add_(index_dense_t* index, usearch_key_t key, void const* vector, s
54
54
  }
55
55
  }
56
56
 
57
- bool get_(index_dense_t* index, usearch_key_t key, size_t count, void* vector, scalar_kind_t kind) {
57
+ std::size_t get_(index_dense_t* index, usearch_key_t key, size_t count, void* vector, scalar_kind_t kind) {
58
58
  switch (kind) {
59
59
  case scalar_kind_t::f32_k: return index->get(key, (f32_t*)vector, count);
60
60
  case scalar_kind_t::f64_k: return index->get(key, (f64_t*)vector, count);
@@ -103,6 +103,11 @@ USEARCH_EXPORT void usearch_free(usearch_index_t index, usearch_error_t*) {
103
103
  delete reinterpret_cast<index_dense_t*>(index);
104
104
  }
105
105
 
106
+ USEARCH_EXPORT size_t usearch_serialized_length(usearch_index_t index, usearch_error_t*) {
107
+ assert(index);
108
+ return reinterpret_cast<index_dense_t*>(index)->serialized_length();
109
+ }
110
+
106
111
  USEARCH_EXPORT void usearch_save(usearch_index_t index, char const* path, usearch_error_t* error) {
107
112
 
108
113
  assert(index && path && error);
@@ -127,6 +132,35 @@ USEARCH_EXPORT void usearch_view(usearch_index_t index, char const* path, usearc
127
132
  *error = result.error.release();
128
133
  }
129
134
 
135
+ USEARCH_EXPORT void usearch_save_buffer(usearch_index_t index, void* buffer, size_t length, usearch_error_t* error) {
136
+
137
+ assert(index && buffer && length && error);
138
+ memory_mapped_file_t memory_map((byte_t*)buffer, length);
139
+ serialization_result_t result = reinterpret_cast<index_dense_t*>(index)->save(std::move(memory_map));
140
+ if (!result)
141
+ *error = result.error.release();
142
+ }
143
+
144
+ USEARCH_EXPORT void usearch_load_buffer(usearch_index_t index, void const* buffer, size_t length,
145
+ usearch_error_t* error) {
146
+
147
+ assert(index && buffer && length && error);
148
+ memory_mapped_file_t memory_map((byte_t*)buffer, length);
149
+ serialization_result_t result = reinterpret_cast<index_dense_t*>(index)->load(std::move(memory_map));
150
+ if (!result)
151
+ *error = result.error.release();
152
+ }
153
+
154
+ USEARCH_EXPORT void usearch_view_buffer(usearch_index_t index, void const* buffer, size_t length,
155
+ usearch_error_t* error) {
156
+
157
+ assert(index && buffer && length && error);
158
+ memory_mapped_file_t memory_map((byte_t*)buffer, length);
159
+ serialization_result_t result = reinterpret_cast<index_dense_t*>(index)->view(std::move(memory_map));
160
+ if (!result)
161
+ *error = result.error.release();
162
+ }
163
+
130
164
  USEARCH_EXPORT size_t usearch_size(usearch_index_t index, usearch_error_t*) { //
131
165
  return reinterpret_cast<index_dense_t*>(index)->size();
132
166
  }
package/c/test.c CHANGED
@@ -13,46 +13,18 @@
13
13
  }
14
14
 
15
15
 
16
- void parse_vectors_from_file(char const* file_path, size_t* count, size_t* dimension, float** data) {
17
-
18
- // Open the file
19
- struct stat st = {};
20
- ASSERT(stat(file_path, &st) == 0, "Failed to get metadata of the file");
21
- FILE* file = fopen(file_path, "rb");
22
- ASSERT(file, "Failed to open the file");
23
-
24
- // Read vectors dimension and calculate count
25
- int dim = 0;
26
- size_t read = fread(&dim, sizeof(int), 1, file);
27
- ASSERT(read == 1, "Failed to read from the file");
28
- ASSERT(st.st_size % ((dim + 1) * sizeof(float)) == 0, "File does not contain a whole number of vectors");
29
- *dimension = (size_t)dim;
30
- *count = st.st_size / ((dim + 1) * sizeof(float));
31
-
32
- // Allocate memory for the vectors' data
33
- *data = (float*)malloc(*count * *dimension * sizeof(float));
34
- ASSERT(*data, "Failed to allocate memory");
35
-
36
- // Read the data
37
- for (size_t i = 0; i < *count; ++i) {
38
- read = fread(*data + i * *dimension, sizeof(float), *dimension, file);
39
- ASSERT(read == *dimension, "Failed to read from the file");
40
- if (i == *count - 1)
41
- break;
42
-
43
- // Skip
44
- float skip;
45
- read = fread(&skip, sizeof(float), 1, file);
46
- ASSERT(read == 1, "Failed to read from the file");
47
- }
48
-
49
- fclose(file);
16
+ float* create_vectors(size_t count, size_t dimensions) {
17
+ float* data = (float*)malloc(count * dimensions * sizeof(float));
18
+ ASSERT(data, "Failed to allocate memory");
19
+ for (size_t idx = 0; idx < count * dimensions; ++idx)
20
+ data[idx] = (float)rand() / (float)RAND_MAX;
21
+ return data;
50
22
  }
51
23
 
52
- usearch_init_options_t create_options(size_t vector_dimension) {
24
+ usearch_init_options_t create_options(size_t dimensions) {
53
25
  usearch_init_options_t opts;
54
26
  opts.connectivity = 2; // 32 in faiss
55
- opts.dimensions = vector_dimension;
27
+ opts.dimensions = dimensions;
56
28
  opts.expansion_add = 40; // 40 in faiss
57
29
  opts.expansion_search = 16; // 10 in faiss
58
30
  opts.metric_kind = usearch_metric_ip_k;
@@ -61,14 +33,13 @@ usearch_init_options_t create_options(size_t vector_dimension) {
61
33
  return opts;
62
34
  }
63
35
 
64
- void test_init(size_t vectors_count, size_t vector_dimension) {
36
+ void test_init(size_t collection_size, size_t dimensions) {
65
37
  printf("Test: Index Initialization...\n");
66
38
 
67
39
  // Init index
68
- usearch_index_t idx = NULL;
69
40
  usearch_error_t error = NULL;
70
- usearch_init_options_t opts = create_options(vector_dimension);
71
- idx = usearch_init(&opts, &error);
41
+ usearch_init_options_t opts = create_options(dimensions);
42
+ usearch_index_t idx = usearch_init(&opts, &error);
72
43
  ASSERT(!error, error);
73
44
  usearch_free(idx, &error);
74
45
  ASSERT(!error, error);
@@ -79,15 +50,15 @@ void test_init(size_t vectors_count, size_t vector_dimension) {
79
50
 
80
51
  ASSERT(usearch_size(idx, &error) == 0, error);
81
52
  ASSERT(usearch_capacity(idx, &error) == 0, error);
82
- ASSERT(usearch_dimensions(idx, &error) == vector_dimension, error);
53
+ ASSERT(usearch_dimensions(idx, &error) == dimensions, error);
83
54
  ASSERT(usearch_connectivity(idx, &error) == opts.connectivity, error);
84
55
 
85
56
  // Reserve
86
- usearch_reserve(idx, vectors_count, &error);
57
+ usearch_reserve(idx, collection_size, &error);
87
58
  ASSERT(!error, error);
88
59
  ASSERT(usearch_size(idx, &error) == 0, error);
89
- ASSERT(usearch_capacity(idx, &error) == vectors_count, error);
90
- ASSERT(usearch_dimensions(idx, &error) == vector_dimension, error);
60
+ ASSERT(usearch_capacity(idx, &error) == collection_size, error);
61
+ ASSERT(usearch_dimensions(idx, &error) == dimensions, error);
91
62
  ASSERT(usearch_connectivity(idx, &error) == opts.connectivity, error);
92
63
 
93
64
  usearch_free(idx, &error);
@@ -96,110 +67,148 @@ void test_init(size_t vectors_count, size_t vector_dimension) {
96
67
  printf("Test: Index Initialization - PASSED\n");
97
68
  }
98
69
 
99
- void test_add_vector(size_t vectors_count, size_t vector_dimension, float const* data) {
70
+ void test_add_vector(size_t collection_size, size_t dimensions) {
100
71
  printf("Test: Add Vector...\n");
101
72
 
102
- usearch_index_t idx = NULL;
103
73
  usearch_error_t error = NULL;
104
- usearch_init_options_t opts = create_options(vector_dimension);
105
- idx = usearch_init(&opts, &error);
106
- usearch_reserve(idx, vectors_count, &error);
74
+ usearch_init_options_t opts = create_options(dimensions);
75
+ usearch_index_t idx = usearch_init(&opts, &error);
76
+ usearch_reserve(idx, collection_size, &error);
107
77
 
108
78
  // Add vectors
109
- for (size_t i = 0; i < vectors_count; ++i) {
79
+ float* data = create_vectors(collection_size, dimensions);
80
+ for (size_t i = 0; i < collection_size; ++i) {
110
81
  usearch_key_t key = i;
111
- usearch_add(idx, key, data + i * vector_dimension, usearch_scalar_f32_k, &error);
82
+ usearch_add(idx, key, data + i * dimensions, usearch_scalar_f32_k, &error);
112
83
  ASSERT(!error, error);
113
84
  }
114
85
 
115
- ASSERT(usearch_size(idx, &error) == vectors_count, error);
116
- ASSERT(usearch_capacity(idx, &error) == vectors_count, error);
86
+ ASSERT(usearch_size(idx, &error) == collection_size, error);
87
+ ASSERT(usearch_capacity(idx, &error) == collection_size, error);
117
88
 
118
89
  // Check vectors in the index
119
- for (size_t i = 0; i < vectors_count; ++i) {
90
+ for (size_t i = 0; i < collection_size; ++i) {
120
91
  usearch_key_t key = i;
121
92
  ASSERT(usearch_contains(idx, key, &error), error);
122
93
  }
123
94
  ASSERT(!usearch_contains(idx, -1, &error), error); // Non existing key
124
95
 
96
+ free(data);
125
97
  usearch_free(idx, &error);
126
98
  printf("Test: Add Vector - PASSED\n");
127
99
  }
128
100
 
129
- void test_find_vector(size_t vectors_count, size_t vector_dimension, float const* data) {
101
+ void test_find_vector(size_t collection_size, size_t dimensions) {
130
102
  printf("Test: Find Vector...\n");
131
103
 
132
- usearch_index_t idx = NULL;
133
104
  usearch_error_t error = NULL;
134
- usearch_init_options_t opts = create_options(vector_dimension);
135
- idx = usearch_init(&opts, &error);
136
- usearch_reserve(idx, vectors_count, &error);
105
+ usearch_init_options_t opts = create_options(dimensions);
106
+ usearch_index_t idx = usearch_init(&opts, &error);
107
+ usearch_reserve(idx, collection_size, &error);
137
108
 
138
109
  // Create result buffers
139
- int results_count = 10;
110
+ int results_count = collection_size;
140
111
  usearch_key_t* keys = (usearch_key_t*)malloc(results_count * sizeof(usearch_key_t));
141
112
  float* distances = (float*)malloc(results_count * sizeof(float));
142
113
  ASSERT(keys && distances, "Failed to allocate memory");
143
114
 
144
115
  // Add vectors
145
- for (size_t i = 0; i < vectors_count; ++i) {
116
+ float* data = create_vectors(collection_size, dimensions);
117
+ for (size_t i = 0; i < collection_size; ++i) {
146
118
  usearch_key_t key = i;
147
- usearch_add(idx, key, data + i * vector_dimension, usearch_scalar_f32_k, &error);
119
+ usearch_add(idx, key, data + i * dimensions, usearch_scalar_f32_k, &error);
148
120
  ASSERT(!error, error);
149
121
  }
150
122
 
151
123
  // Find the vectors
152
- for (size_t i = 0; i < vectors_count; i++) {
153
- const void *query_vector = data + i * vector_dimension;
124
+ for (size_t i = 0; i < collection_size; i++) {
125
+ const void *query_vector = data + i * dimensions;
154
126
  size_t found_count = usearch_search(idx, query_vector, usearch_scalar_f32_k, results_count, keys, distances, &error);
155
127
  ASSERT(!error, error);
156
128
  ASSERT(found_count = results_count, "Vector is missing");
157
129
  }
158
130
 
131
+ free(data);
132
+ free(keys);
133
+ free(distances);
159
134
  usearch_free(idx, &error);
160
135
  printf("Test: Find Vector - PASSED\n");
161
136
  }
162
137
 
163
- void test_remove_vector(size_t vectors_count, size_t vector_dimension, float const* data) {
138
+ void test_get_vector(size_t collection_size, size_t dimensions) {
139
+ printf("Test: Get Vector...\n");
140
+
141
+ usearch_error_t error = NULL;
142
+ usearch_init_options_t opts = create_options(dimensions);
143
+ opts.multi = true;
144
+ usearch_index_t idx = usearch_init(&opts, &error);
145
+ usearch_reserve(idx, collection_size, &error);
146
+
147
+ // Create result buffers
148
+ int results_count = collection_size;
149
+ float* vectors = (float*)malloc(results_count * dimensions * sizeof(float));
150
+ ASSERT(vectors, "Failed to allocate memory");
151
+
152
+ // Add multiple vectors with SAME key
153
+ usearch_key_t const key = 1;
154
+ float* data = create_vectors(collection_size, dimensions);
155
+ for (size_t i = 0; i < collection_size; i++) {
156
+ usearch_add(idx, key, data + i * dimensions, usearch_scalar_f32_k, &error);
157
+ ASSERT(!error, error);
158
+ }
159
+
160
+ // Retrieve vectors from index
161
+ size_t count = usearch_get(idx, key, collection_size, vectors, usearch_scalar_f32_k, &error);
162
+ ASSERT(count == results_count, "Vector is missing");
163
+
164
+ free(vectors);
165
+ free(data);
166
+ usearch_free(idx, &error);
167
+
168
+ printf("Test: Get Vector - PASSED\n");
169
+ }
170
+
171
+ void test_remove_vector(size_t collection_size, size_t dimensions) {
164
172
  printf("Test: Remove Vector...\n");
165
173
 
166
- usearch_index_t idx = NULL;
167
174
  usearch_error_t error = NULL;
168
- usearch_init_options_t opts = create_options(vector_dimension);
169
- idx = usearch_init(&opts, &error);
170
- usearch_reserve(idx, vectors_count, &error);
175
+ usearch_init_options_t opts = create_options(dimensions);
176
+ usearch_index_t idx = usearch_init(&opts, &error);
177
+ usearch_reserve(idx, collection_size, &error);
171
178
 
172
179
  // Add vectors
173
- for (size_t i = 0; i < vectors_count; ++i) {
180
+ float* data = create_vectors(collection_size, dimensions);
181
+ for (size_t i = 0; i < collection_size; ++i) {
174
182
  usearch_key_t key = i;
175
- usearch_add(idx, key, data + i * vector_dimension, usearch_scalar_f32_k, &error);
183
+ usearch_add(idx, key, data + i * dimensions, usearch_scalar_f32_k, &error);
176
184
  ASSERT(!error, error);
177
185
  }
178
186
 
179
187
  // Remove the vectors
180
- for (size_t i = 0; i < vectors_count; i++) {
188
+ for (size_t i = 0; i < collection_size; i++) {
181
189
  usearch_key_t key = i;
182
190
  usearch_remove(idx, key, &error);
183
- ASSERT(error, "Currently, Remove is not supported");
191
+ ASSERT(!error, "Currently, Remove is not supported");
184
192
  }
185
193
 
194
+ free(data);
186
195
  usearch_free(idx, &error);
187
196
  printf("Test: Remove Vector - PASSED\n");
188
197
  }
189
198
 
190
- void test_save_load(size_t vectors_count, size_t vector_dimension, float const* data) {
199
+ void test_save_load(size_t collection_size, size_t dimensions) {
191
200
  printf("Test: Save/Load...\n");
192
201
 
193
- usearch_index_t idx = NULL;
194
202
  usearch_error_t error = NULL;
195
- usearch_init_options_t opts = create_options(vector_dimension);
196
- idx = usearch_init(&opts, &error);
197
- usearch_reserve(idx, vectors_count, &error);
203
+ usearch_init_options_t opts = create_options(dimensions);
204
+ usearch_index_t idx = usearch_init(&opts, &error);
205
+ usearch_reserve(idx, collection_size, &error);
198
206
 
199
207
  // Add vectors
200
- for (size_t i = 0; i < vectors_count; ++i) {
208
+ float* data = create_vectors(collection_size, dimensions);
209
+ for (size_t i = 0; i < collection_size; ++i) {
201
210
  usearch_key_t key = i;
202
- usearch_add(idx, key, data + i * vector_dimension, usearch_scalar_f32_k, &error);
211
+ usearch_add(idx, key, data + i * dimensions, usearch_scalar_f32_k, &error);
203
212
  ASSERT(!error, error);
204
213
  }
205
214
 
@@ -217,34 +226,35 @@ void test_save_load(size_t vectors_count, size_t vector_dimension, float const*
217
226
  // Load
218
227
  usearch_load(idx, "usearch_index.bin", &error);
219
228
  ASSERT(!error, error);
220
- ASSERT(usearch_size(idx, &error) == vectors_count, error);
221
- ASSERT(usearch_capacity(idx, &error) == vectors_count, error);
222
- ASSERT(usearch_dimensions(idx, &error) == vector_dimension, error);
229
+ ASSERT(usearch_size(idx, &error) == collection_size, error);
230
+ ASSERT(usearch_capacity(idx, &error) == collection_size, error);
231
+ ASSERT(usearch_dimensions(idx, &error) == dimensions, error);
223
232
  ASSERT(usearch_connectivity(idx, &error) == opts.connectivity, error);
224
233
 
225
234
  // Check vectors in the index
226
- for (size_t i = 0; i < vectors_count; ++i) {
235
+ for (size_t i = 0; i < collection_size; ++i) {
227
236
  usearch_key_t key = i;
228
237
  ASSERT(usearch_contains(idx, key, &error), error);
229
238
  }
230
239
 
240
+ free(data);
231
241
  usearch_free(idx, &error);
232
242
  printf("Test: Save/Load - PASSED\n");
233
243
  }
234
244
 
235
- void test_view(size_t vectors_count, size_t vector_dimension, float const* data) {
245
+ void test_view(size_t collection_size, size_t dimensions) {
236
246
  printf("Test: View...\n");
237
247
 
238
- usearch_index_t idx = NULL;
239
248
  usearch_error_t error = NULL;
240
- usearch_init_options_t opts = create_options(vector_dimension);
241
- idx = usearch_init(&opts, &error);
242
- usearch_reserve(idx, vectors_count, &error);
249
+ usearch_init_options_t opts = create_options(dimensions);
250
+ usearch_index_t idx = usearch_init(&opts, &error);
251
+ usearch_reserve(idx, collection_size, &error);
243
252
 
244
253
  // Add vectors
245
- for (size_t i = 0; i < vectors_count; ++i) {
254
+ float* data = create_vectors(collection_size, dimensions);
255
+ for (size_t i = 0; i < collection_size; ++i) {
246
256
  usearch_key_t key = i;
247
- usearch_add(idx, key, data + i * vector_dimension, usearch_scalar_f32_k, &error);
257
+ usearch_add(idx, key, data + i * dimensions, usearch_scalar_f32_k, &error);
248
258
  ASSERT(!error, error);
249
259
  }
250
260
 
@@ -262,33 +272,26 @@ void test_view(size_t vectors_count, size_t vector_dimension, float const* data)
262
272
  usearch_view(idx, "usearch_index.bin", &error);
263
273
  ASSERT(!error, error);
264
274
 
275
+ free(data);
265
276
  usearch_free(idx, &error);
266
277
  printf("Test: View - PASSED\n");
267
278
  }
268
279
 
269
- int main(int argc, char* argv[]) {
270
- // Resolve file path
271
- char const* filename = "sample.fvecs";
272
- if (argc == 2)
273
- filename = argv[1];
274
-
275
- // Read data from the file
276
- float* data = NULL;
277
- size_t vectors_count = 0;
278
- size_t vector_dimension = 0;
279
- parse_vectors_from_file(filename, &vectors_count, &vector_dimension, &data);
280
- vectors_count = vectors_count > 100000 ? 100000 : vectors_count; // Just limit vectors
281
-
282
- // Test
283
- test_init(vectors_count, vector_dimension);
284
- test_add_vector(vectors_count, vector_dimension, data);
285
- test_find_vector(vectors_count, vector_dimension, data);
286
- test_remove_vector(vectors_count, vector_dimension, data);
287
- test_save_load(vectors_count, vector_dimension, data);
288
- test_view(vectors_count, vector_dimension, data);
289
-
290
- // Free vectors' buffer
291
- free(data);
280
+ int main() {
281
+
282
+ size_t collection_sizes[] = {11, 512};
283
+ size_t dimensions[] = {83, 1};
284
+ for (size_t idx = 0; idx < sizeof(collection_sizes) / sizeof(collection_sizes[0]); ++idx) {
285
+ for (size_t jdx = 0; jdx < sizeof(dimensions) / sizeof(dimensions[0]); ++jdx) {
286
+ test_init(collection_sizes[idx], dimensions[jdx]);
287
+ test_add_vector(collection_sizes[idx], dimensions[jdx]);
288
+ test_find_vector(collection_sizes[idx], dimensions[jdx]);
289
+ test_get_vector(collection_sizes[idx], dimensions[jdx]);
290
+ test_remove_vector(collection_sizes[idx], dimensions[jdx]);
291
+ test_save_load(collection_sizes[idx], dimensions[jdx]);
292
+ test_view(collection_sizes[idx], dimensions[jdx]);
293
+ }
294
+ }
292
295
 
293
296
  return 0;
294
297
  }
package/c/usearch.h CHANGED
@@ -109,6 +109,12 @@ USEARCH_EXPORT usearch_index_t usearch_init(usearch_init_options_t* options, use
109
109
  */
110
110
  USEARCH_EXPORT void usearch_free(usearch_index_t, usearch_error_t* error);
111
111
 
112
+ /**
113
+ * @brief Reports expected file size after serialization.
114
+ * @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
115
+ */
116
+ USEARCH_EXPORT size_t usearch_serialized_length(usearch_index_t, usearch_error_t* error);
117
+
112
118
  /**
113
119
  * @brief Saves the index to a file.
114
120
  * @param[in] path The file path where the index will be saved.
@@ -124,12 +130,33 @@ USEARCH_EXPORT void usearch_save(usearch_index_t, char const* path, usearch_erro
124
130
  USEARCH_EXPORT void usearch_load(usearch_index_t, char const* path, usearch_error_t* error);
125
131
 
126
132
  /**
127
- * @brief Creates a view of the index from a file without loading it into memory.
133
+ * @brief Creates a view of the index from a file without copying it into memory.
128
134
  * @param[in] path The file path from where the view will be created.
129
135
  * @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
130
136
  */
131
137
  USEARCH_EXPORT void usearch_view(usearch_index_t, char const* path, usearch_error_t* error);
132
138
 
139
+ /**
140
+ * @brief Saves the index to an in-memory buffer.
141
+ * @param[in] buffer The in-memory continuous buffer where the index will be saved.
142
+ * @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
143
+ */
144
+ USEARCH_EXPORT void usearch_save_buffer(usearch_index_t, void* buffer, size_t length, usearch_error_t* error);
145
+
146
+ /**
147
+ * @brief Loads the index from an in-memory buffer.
148
+ * @param[in] buffer The in-memory continuous buffer from where the index will be loaded.
149
+ * @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
150
+ */
151
+ USEARCH_EXPORT void usearch_load_buffer(usearch_index_t, void const* buffer, size_t length, usearch_error_t* error);
152
+
153
+ /**
154
+ * @brief Creates a view of the index from an in-memory buffer without copying it into memory.
155
+ * @param[in] buffer The in-memory continuous buffer from where the view will be created.
156
+ * @param[out] error Pointer to a string where the error message will be stored, if an error occurs.
157
+ */
158
+ USEARCH_EXPORT void usearch_view_buffer(usearch_index_t, void const* buffer, size_t length, usearch_error_t* error);
159
+
133
160
  USEARCH_EXPORT size_t usearch_size(usearch_index_t, usearch_error_t* error);
134
161
  USEARCH_EXPORT size_t usearch_capacity(usearch_index_t, usearch_error_t* error);
135
162
  USEARCH_EXPORT size_t usearch_dimensions(usearch_index_t, usearch_error_t* error);
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 = '1.2.1'
7
+ version = '1.3.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/test.cpp CHANGED
@@ -118,7 +118,7 @@ void test_cosine(index_at& index, std::vector<std::vector<scalar_at>> const& vec
118
118
  }
119
119
 
120
120
  // Search again over mapped index
121
- // file_head_result_t head = index_dense_metadata("tmp.usearch");
121
+ // file_head_result_t head = index_dense_metadata_from_path("tmp.usearch");
122
122
  // expect(head.size == 3);
123
123
  index.view("tmp.usearch");
124
124
  matched_count = index.search(vector_first, 5, args...).dump_to(matched_keys, matched_distances);
@@ -140,7 +140,7 @@ void test_cosine(index_at& index, std::vector<std::vector<scalar_at>> const& vec
140
140
 
141
141
  // Check metadata
142
142
  if constexpr (punned_ak) {
143
- index_dense_metadata_result_t meta = index_dense_metadata("tmp.usearch");
143
+ index_dense_metadata_result_t meta = index_dense_metadata_from_path("tmp.usearch");
144
144
  expect(bool(meta));
145
145
  }
146
146
  }
@@ -19,13 +19,13 @@ The default values vary drastically.
19
19
  Below are the performance numbers for a benchmark running on the 64 cores of AWS `c7g.metal` "Graviton 3"-based instances.
20
20
  The main columns are:
21
21
 
22
- - **Add**: Number of insertion Queries Per Second.
23
- - **Search**: Number search Queries Per Second.
24
- - **Recall @1**: How often does approximate search yield the exact best match?
22
+ - __Add__: Number of insertion Queries Per Second.
23
+ - __Search__: Number search Queries Per Second.
24
+ - __Recall @1__: How often does approximate search yield the exact best match?
25
25
 
26
26
  ### Different "connectivity"
27
27
 
28
- | Vectors | Connectivity | EF @ A | EF @ S | **Add**, QPS | **Search**, QPS | **Recall @1** |
28
+ | Vectors | Connectivity | EF @ A | EF @ S | __Add__, QPS | __Search__, QPS | __Recall @1__ |
29
29
  | :--------- | :----------: | :----: | :----: | :----------: | :-------------: | ------------: |
30
30
  | `f32` x256 | 16 | 128 | 64 | 75'640 | 131'654 | 99.3% |
31
31
  | `f32` x256 | 12 | 128 | 64 | 81'747 | 149'728 | 99.0% |
@@ -33,7 +33,7 @@ The main columns are:
33
33
 
34
34
  ### Different "expansion factors"
35
35
 
36
- | Vectors | Connectivity | EF @ A | EF @ S | **Add**, QPS | **Search**, QPS | **Recall @1** |
36
+ | Vectors | Connectivity | EF @ A | EF @ S | __Add__, QPS | __Search__, QPS | __Recall @1__ |
37
37
  | :--------- | :----------: | :----: | :----: | :----------: | :-------------: | ------------: |
38
38
  | `f32` x256 | 16 | 128 | 64 | 75'640 | 131'654 | 99.3% |
39
39
  | `f32` x256 | 16 | 64 | 32 | 128'644 | 228'422 | 97.2% |
@@ -41,7 +41,7 @@ The main columns are:
41
41
 
42
42
  ### Different vectors "quantization"
43
43
 
44
- | Vectors | Connectivity | EF @ A | EF @ S | **Add**, QPS | **Search**, QPS | **Recall @1** |
44
+ | Vectors | Connectivity | EF @ A | EF @ S | __Add__, QPS | __Search__, QPS | __Recall @1__ |
45
45
  | :----------- | :----------: | :----: | :----: | :----------: | :-------------: | ------------: |
46
46
  | `f32` x256 | 16 | 128 | 64 | 87'995 | 171'856 | 99.1% |
47
47
  | `f16` x256 | 16 | 128 | 64 | 87'270 | 153'788 | 98.4% |