usearch 0.22.3 → 1.1.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.
Files changed (93) hide show
  1. package/.github/workflows/prerelease.yml +3 -3
  2. package/.releaserc +10 -2
  3. package/.vscode/launch.json +10 -10
  4. package/.vscode/settings.json +9 -1
  5. package/.vscode/tasks.json +4 -4
  6. package/CMakeLists.txt +6 -1
  7. package/Cargo.toml +2 -2
  8. package/README.md +35 -41
  9. package/build.rs +2 -2
  10. package/c/CMakeLists.txt +48 -0
  11. package/c/README.md +34 -0
  12. package/c/lib.cpp +83 -72
  13. package/c/test.c +20 -20
  14. package/c/usearch.h +132 -32
  15. package/conanfile.py +1 -1
  16. package/cpp/CMakeLists.txt +3 -1
  17. package/cpp/README.md +16 -16
  18. package/cpp/bench.cpp +50 -126
  19. package/cpp/test.cpp +164 -80
  20. package/docs/benchmarks.md +26 -5
  21. package/docs/c/reference.rst +3 -3
  22. package/docs/compilation.md +31 -49
  23. package/docs/cpp/index.rst +1 -1
  24. package/docs/cpp/reference.rst +4 -4
  25. package/golang/lib.go +275 -141
  26. package/golang/lib_test.go +105 -0
  27. package/include/usearch/index.hpp +1681 -1656
  28. package/include/usearch/index_dense.hpp +1400 -0
  29. package/include/usearch/index_plugins.hpp +1263 -0
  30. package/java/README.md +1 -1
  31. package/java/cloud/unum/usearch/Index.java +10 -10
  32. package/java/cloud/unum/usearch/cloud_unum_usearch_Index.cpp +34 -37
  33. package/java/test/IndexTest.java +1 -1
  34. package/javascript/README.md +6 -6
  35. package/javascript/lib.cpp +156 -55
  36. package/javascript/test.js +26 -11
  37. package/javascript/usearch.d.ts +102 -0
  38. package/javascript/usearch.js +2 -118
  39. package/objc/USearchObjective.mm +57 -31
  40. package/objc/include/USearchObjective.h +32 -22
  41. package/package.json +5 -3
  42. package/python/README.md +23 -14
  43. package/python/lib.cpp +468 -444
  44. package/python/scripts/bench.ipynb +3 -2
  45. package/python/scripts/bench.py +4 -4
  46. package/python/scripts/index_faiss.py +5 -5
  47. package/python/scripts/join.py +4 -4
  48. package/python/scripts/test.py +152 -61
  49. package/python/scripts/test_jit.py +10 -12
  50. package/python/scripts/test_sparse.py +32 -0
  51. package/python/usearch/client.py +26 -28
  52. package/python/usearch/eval.py +73 -23
  53. package/python/usearch/index.py +520 -228
  54. package/python/usearch/io.py +57 -25
  55. package/python/usearch/numba.py +4 -4
  56. package/python/usearch/server.py +10 -10
  57. package/rust/lib.cpp +34 -30
  58. package/rust/lib.hpp +14 -12
  59. package/rust/lib.rs +8 -7
  60. package/setup.py +5 -2
  61. package/simsimd/.github/workflows/prerelease.yml +73 -0
  62. package/simsimd/.github/workflows/release.yml +110 -0
  63. package/simsimd/.github/workflows/update_version.sh +3 -0
  64. package/simsimd/.releaserc +77 -0
  65. package/simsimd/.vscode/launch.json +27 -1
  66. package/simsimd/.vscode/settings.json +22 -1
  67. package/simsimd/.vscode/tasks.json +6 -0
  68. package/simsimd/CMakeLists.txt +3 -2
  69. package/simsimd/README.md +82 -46
  70. package/simsimd/bench.cxx +63 -24
  71. package/simsimd/include/simsimd/simsimd.h +88 -66
  72. package/simsimd/include/simsimd/simsimd_chem.h +126 -0
  73. package/simsimd/package.json +8 -0
  74. package/simsimd/pyproject.toml +42 -0
  75. package/simsimd/python/python.c +81 -0
  76. package/simsimd/python/run.sh +1 -0
  77. package/simsimd/python/test.c +109 -0
  78. package/simsimd/python/test.py +23 -0
  79. package/simsimd/setup.py +44 -0
  80. package/swift/Index+Sugar.swift +75 -23
  81. package/swift/README.md +26 -2
  82. package/swift/Test.swift +19 -7
  83. package/swift/USearch.swift +2 -2
  84. package/tsconfig.json +8 -0
  85. package/wasm/CMakeLists.txt +1 -0
  86. package/wasmer.toml +1 -1
  87. package/wolfram/CMakeLists.txt +1 -0
  88. package/wolfram/lib.cpp +11 -13
  89. package/golang/usearch.h +0 -9
  90. package/golang/usearch_par_test.go +0 -40
  91. package/golang/usearch_test.go +0 -82
  92. package/include/usearch/index_punned_dense.hpp +0 -1001
  93. package/include/usearch/index_punned_helpers.hpp +0 -676
@@ -1,676 +0,0 @@
1
- #pragma once
2
- #include <stdlib.h> // `aligned_alloc`
3
-
4
- #include <cstring> // `std::strncmp`
5
- #include <numeric> // `std::iota`
6
- #include <thread> // `std::thread`
7
- #include <vector> // `std::vector`
8
-
9
- #include <usearch/index.hpp> // `expected_gt` and macros
10
-
11
- #if USEARCH_USE_OPENMP
12
- #include <omp.h> // `omp_get_num_threads()`
13
- #endif
14
-
15
- #if defined(USEARCH_DEFINED_LINUX)
16
- #include <sys/auxv.h> // `getauxval()`
17
- #endif
18
-
19
- #if defined(USEARCH_DEFINED_ARM)
20
- #include <arm_fp16.h> // `__fp16`
21
- #endif
22
-
23
- #if !defined(USEARCH_USE_NATIVE_F16)
24
- #if defined(__AVX512F__)
25
- #define USEARCH_USE_NATIVE_F16 1
26
- #elif defined(USEARCH_DEFINED_ARM)
27
- #define USEARCH_USE_NATIVE_F16 1
28
- #else
29
- #define USEARCH_USE_NATIVE_F16 0
30
- #include <fp16/fp16.h>
31
- #endif
32
- #else
33
- #define USEARCH_USE_NATIVE_F16 0
34
- #include <fp16/fp16.h>
35
- #endif
36
-
37
- #if !defined(USEARCH_USE_SIMSIMD)
38
- #define USEARCH_USE_SIMSIMD 0
39
- #endif
40
-
41
- #if USEARCH_USE_SIMSIMD
42
- #include <simsimd/simsimd.h>
43
- #endif
44
-
45
- namespace unum {
46
- namespace usearch {
47
-
48
- template <typename at, typename compare_at> inline at clamp(at v, at lo, at hi, compare_at comp) noexcept {
49
- return comp(v, lo) ? lo : comp(hi, v) ? hi : v;
50
- }
51
- template <typename at> inline at clamp(at v, at lo, at hi) noexcept {
52
- return usearch::clamp(v, lo, hi, std::less<at>{});
53
- }
54
-
55
- inline bool str_equals(char const* begin, std::size_t len, char const* other_begin) noexcept {
56
- std::size_t other_len = std::strlen(other_begin);
57
- return len == other_len && std::strncmp(begin, other_begin, len) == 0;
58
- }
59
-
60
- enum class isa_t {
61
- auto_k,
62
- neon_k,
63
- sve_k,
64
- avx2_k,
65
- avx512_k,
66
- };
67
-
68
- inline char const* isa_name(isa_t isa) noexcept {
69
- switch (isa) {
70
- case isa_t::auto_k: return "auto";
71
- case isa_t::neon_k: return "neon";
72
- case isa_t::sve_k: return "sve";
73
- case isa_t::avx2_k: return "avx2";
74
- case isa_t::avx512_k: return "avx512";
75
- default: return "";
76
- }
77
- }
78
-
79
- inline bool hardware_supports(isa_t isa) noexcept {
80
- #if defined(USEARCH_DEFINED_ARM) && defined(USEARCH_DEFINED_LINUX)
81
- unsigned long capabilities = getauxval(AT_HWCAP);
82
- switch (isa) {
83
- case isa_t::neon_k: return true; // Must be supported on 64-bit Arm
84
- case isa_t::sve_k: return capabilities & HWCAP_SVE;
85
- default: return false;
86
- }
87
- #endif
88
-
89
- #if defined(USEARCH_DEFINED_X86) && defined(USEARCH_DEFINED_GCC)
90
- __builtin_cpu_init();
91
- switch (isa) {
92
- case isa_t::avx2_k: return __builtin_cpu_supports("avx2");
93
- case isa_t::avx512_k: return __builtin_cpu_supports("avx512f");
94
- default: return false;
95
- }
96
- #endif
97
-
98
- (void)isa;
99
- return false;
100
- }
101
-
102
- inline std::size_t bytes_per_scalar(scalar_kind_t accuracy) noexcept {
103
- switch (accuracy) {
104
- case scalar_kind_t::f32_k: return 4;
105
- case scalar_kind_t::f16_k: return 2;
106
- case scalar_kind_t::f64_k: return 8;
107
- case scalar_kind_t::f8_k: return 1;
108
- case scalar_kind_t::b1x8_k: return 1;
109
- default: return 0;
110
- }
111
- }
112
-
113
- inline char const* scalar_kind_name(scalar_kind_t accuracy) noexcept {
114
- switch (accuracy) {
115
- case scalar_kind_t::f32_k: return "f32";
116
- case scalar_kind_t::f16_k: return "f16";
117
- case scalar_kind_t::f64_k: return "f64";
118
- case scalar_kind_t::f8_k: return "f8";
119
- case scalar_kind_t::b1x8_k: return "b1x8";
120
- default: return "";
121
- }
122
- }
123
-
124
- inline char const* metric_kind_name(metric_kind_t metric) noexcept {
125
- switch (metric) {
126
- case metric_kind_t::unknown_k: return "unknown";
127
- case metric_kind_t::ip_k: return "ip";
128
- case metric_kind_t::cos_k: return "cos";
129
- case metric_kind_t::l2sq_k: return "l2sq";
130
- case metric_kind_t::pearson_k: return "pearson";
131
- case metric_kind_t::haversine_k: return "haversine";
132
- case metric_kind_t::jaccard_k: return "jaccard";
133
- case metric_kind_t::hamming_k: return "hamming";
134
- case metric_kind_t::tanimoto_k: return "tanimoto";
135
- case metric_kind_t::sorensen_k: return "sorensen";
136
- }
137
- return "";
138
- }
139
- inline expected_gt<scalar_kind_t> scalar_kind_from_name(char const* name, std::size_t len) {
140
- expected_gt<scalar_kind_t> parsed;
141
- if (str_equals(name, len, "f32"))
142
- parsed.result = scalar_kind_t::f32_k;
143
- else if (str_equals(name, len, "f64"))
144
- parsed.result = scalar_kind_t::f64_k;
145
- else if (str_equals(name, len, "f16"))
146
- parsed.result = scalar_kind_t::f16_k;
147
- else if (str_equals(name, len, "f8"))
148
- parsed.result = scalar_kind_t::f8_k;
149
- else
150
- parsed.failed("Unknown type, choose: f32, f16, f64, f8");
151
- return parsed;
152
- }
153
-
154
- inline expected_gt<metric_kind_t> metric_from_name(char const* name, std::size_t len) {
155
- expected_gt<metric_kind_t> parsed;
156
- if (str_equals(name, len, "l2sq") || str_equals(name, len, "euclidean_sq")) {
157
- parsed.result = metric_kind_t::l2sq_k;
158
- } else if (str_equals(name, len, "ip") || str_equals(name, len, "inner") || str_equals(name, len, "dot")) {
159
- parsed.result = metric_kind_t::ip_k;
160
- } else if (str_equals(name, len, "cos") || str_equals(name, len, "angular")) {
161
- parsed.result = metric_kind_t::cos_k;
162
- } else if (str_equals(name, len, "haversine")) {
163
- parsed.result = metric_kind_t::haversine_k;
164
- } else if (str_equals(name, len, "pearson")) {
165
- parsed.result = metric_kind_t::pearson_k;
166
- } else if (str_equals(name, len, "hamming")) {
167
- parsed.result = metric_kind_t::hamming_k;
168
- } else if (str_equals(name, len, "tanimoto")) {
169
- parsed.result = metric_kind_t::tanimoto_k;
170
- } else if (str_equals(name, len, "sorensen")) {
171
- parsed.result = metric_kind_t::sorensen_k;
172
- } else
173
- parsed.failed(
174
- "Unknown distance, choose: l2sq, ip, cos, haversine, jaccard, pearson, hamming, tanimoto, sorensen");
175
- return parsed;
176
- }
177
-
178
- using punned_distance_t = float;
179
- using punned_vector_view_t = span_gt<byte_t const>;
180
-
181
- class f8_bits_t;
182
- class f16_bits_t;
183
-
184
- #if USEARCH_USE_NATIVE_F16
185
- #if defined(USEARCH_DEFINED_ARM)
186
- using f16_native_t = __fp16;
187
- #else
188
- using f16_native_t = _Float16;
189
- #endif
190
- using f16_t = f16_native_t;
191
- #else
192
- using f16_native_t = void;
193
- using f16_t = f16_bits_t;
194
- #endif
195
-
196
- inline float f16_to_f32(std::uint16_t u16) noexcept {
197
- #if USEARCH_USE_NATIVE_F16
198
- f16_native_t f16;
199
- std::memcpy(&f16, &u16, sizeof(std::uint16_t));
200
- return float(f16);
201
- #else
202
- return fp16_ieee_to_fp32_value(u16);
203
- #endif
204
- }
205
-
206
- inline std::uint16_t f32_to_f16(float f32) noexcept {
207
- #if USEARCH_USE_NATIVE_F16
208
- f16_native_t f16 = f16_native_t(f32);
209
- std::uint16_t u16;
210
- std::memcpy(&u16, &f16, sizeof(std::uint16_t));
211
- return u16;
212
- #else
213
- return fp16_ieee_from_fp32_value(f32);
214
- #endif
215
- }
216
-
217
- /**
218
- * @brief Numeric type for the IEEE 754 half-precision floating point.
219
- * If hardware support isn't available, falls back to a hardware
220
- * agnostic in-software implementation.
221
- */
222
- class f16_bits_t {
223
- std::uint16_t uint16_{};
224
-
225
- public:
226
- inline f16_bits_t() noexcept : uint16_(0) {}
227
- inline f16_bits_t(f16_bits_t&&) = default;
228
- inline f16_bits_t& operator=(f16_bits_t&&) = default;
229
- inline f16_bits_t(f16_bits_t const&) = default;
230
- inline f16_bits_t& operator=(f16_bits_t const&) = default;
231
-
232
- inline operator float() const noexcept { return f16_to_f32(uint16_); }
233
- inline explicit operator bool() const noexcept { return f16_to_f32(uint16_) > 0.5f; }
234
-
235
- inline f16_bits_t(f8_bits_t) noexcept;
236
- inline f16_bits_t(bool v) noexcept : uint16_(f32_to_f16(v)) {}
237
- inline f16_bits_t(float v) noexcept : uint16_(f32_to_f16(v)) {}
238
- inline f16_bits_t(double v) noexcept : uint16_(f32_to_f16(v)) {}
239
-
240
- inline f16_bits_t operator+(f16_bits_t other) const noexcept { return {float(*this) + float(other)}; }
241
- inline f16_bits_t operator-(f16_bits_t other) const noexcept { return {float(*this) - float(other)}; }
242
- inline f16_bits_t operator*(f16_bits_t other) const noexcept { return {float(*this) * float(other)}; }
243
- inline f16_bits_t operator/(f16_bits_t other) const noexcept { return {float(*this) / float(other)}; }
244
- inline f16_bits_t operator+(float other) const noexcept { return {float(*this) + other}; }
245
- inline f16_bits_t operator-(float other) const noexcept { return {float(*this) - other}; }
246
- inline f16_bits_t operator*(float other) const noexcept { return {float(*this) * other}; }
247
- inline f16_bits_t operator/(float other) const noexcept { return {float(*this) / other}; }
248
- inline f16_bits_t operator+(double other) const noexcept { return {float(*this) + other}; }
249
- inline f16_bits_t operator-(double other) const noexcept { return {float(*this) - other}; }
250
- inline f16_bits_t operator*(double other) const noexcept { return {float(*this) * other}; }
251
- inline f16_bits_t operator/(double other) const noexcept { return {float(*this) / other}; }
252
-
253
- inline f16_bits_t& operator+=(float v) noexcept {
254
- uint16_ = f32_to_f16(v + f16_to_f32(uint16_));
255
- return *this;
256
- }
257
-
258
- inline f16_bits_t& operator-=(float v) noexcept {
259
- uint16_ = f32_to_f16(v - f16_to_f32(uint16_));
260
- return *this;
261
- }
262
-
263
- inline f16_bits_t& operator*=(float v) noexcept {
264
- uint16_ = f32_to_f16(v * f16_to_f32(uint16_));
265
- return *this;
266
- }
267
-
268
- inline f16_bits_t& operator/=(float v) noexcept {
269
- uint16_ = f32_to_f16(v / f16_to_f32(uint16_));
270
- return *this;
271
- }
272
- };
273
-
274
- /**
275
- * @brief Numeric type for uniformly-distributed floating point
276
- * values within [-1,1] range, quantized to integers [-100,100].
277
- */
278
- class f8_bits_t {
279
- std::int8_t int8_{};
280
-
281
- public:
282
- constexpr static float divisor_k = 100.f;
283
- constexpr static std::int8_t min_k = -100;
284
- constexpr static std::int8_t max_k = 100;
285
-
286
- inline f8_bits_t() noexcept : int8_(0) {}
287
- inline f8_bits_t(bool v) noexcept : int8_(v ? max_k : 0) {}
288
-
289
- inline f8_bits_t(f8_bits_t&&) = default;
290
- inline f8_bits_t& operator=(f8_bits_t&&) = default;
291
- inline f8_bits_t(f8_bits_t const&) = default;
292
- inline f8_bits_t& operator=(f8_bits_t const&) = default;
293
-
294
- inline operator float() const noexcept { return float(int8_) / divisor_k; }
295
- inline operator f16_t() const noexcept { return float(int8_) / divisor_k; }
296
- inline operator double() const noexcept { return double(int8_) / divisor_k; }
297
- inline explicit operator bool() const noexcept { return int8_ > (max_k / 2); }
298
- inline explicit operator std::int8_t() const noexcept { return int8_; }
299
- inline explicit operator std::int16_t() const noexcept { return int8_; }
300
- inline explicit operator std::int32_t() const noexcept { return int8_; }
301
- inline explicit operator std::int64_t() const noexcept { return int8_; }
302
-
303
- inline f8_bits_t(f16_t v)
304
- : int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * divisor_k), min_k, max_k)) {}
305
- inline f8_bits_t(float v)
306
- : int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * divisor_k), min_k, max_k)) {}
307
- inline f8_bits_t(double v)
308
- : int8_(usearch::clamp<std::int8_t>(static_cast<std::int8_t>(v * divisor_k), min_k, max_k)) {}
309
- };
310
-
311
- inline f16_bits_t::f16_bits_t(f8_bits_t v) noexcept : f16_bits_t(float(v)) {}
312
-
313
- struct uuid_t {
314
- std::uint8_t octets[16];
315
- };
316
-
317
- /**
318
- * @brief An STL-based executor or a "thread-pool" for parallel execution.
319
- * Isn't efficient for small batches, as it recreates the threads on every call.
320
- */
321
- class executor_stl_t {
322
- std::size_t threads_count_{};
323
-
324
- public:
325
- /**
326
- * @param threads_count The number of threads to be used for parallel execution.
327
- */
328
- executor_stl_t(std::size_t threads_count = 0) noexcept
329
- : threads_count_(threads_count ? threads_count : std::thread::hardware_concurrency()) {}
330
-
331
- /**
332
- * @return Maximum number of threads available to the executor.
333
- */
334
- std::size_t size() const noexcept { return threads_count_; }
335
-
336
- /**
337
- * @brief Executes tasks in bulk using the specified thread-aware function.
338
- * @param tasks The total number of tasks to be executed.
339
- * @param thread_aware_function The thread-aware function to be called for each thread index and task index.
340
- * @throws If an exception occurs during execution of the thread-aware function.
341
- */
342
- template <typename thread_aware_function_at>
343
- void execute_bulk(std::size_t tasks, thread_aware_function_at&& thread_aware_function) noexcept(false) {
344
- std::vector<std::thread> threads_pool;
345
- std::size_t tasks_per_thread = (tasks / threads_count_) + ((tasks % threads_count_) != 0);
346
- for (std::size_t thread_idx = 0; thread_idx != threads_count_; ++thread_idx) {
347
- threads_pool.emplace_back([=]() {
348
- for (std::size_t task_idx = thread_idx * tasks_per_thread;
349
- task_idx < (std::min)(tasks, thread_idx * tasks_per_thread + tasks_per_thread); ++task_idx)
350
- thread_aware_function(thread_idx, task_idx);
351
- });
352
- }
353
- for (std::size_t thread_idx = 0; thread_idx != threads_count_; ++thread_idx)
354
- threads_pool[thread_idx].join();
355
- }
356
-
357
- /**
358
- * @brief Saturates every available thread with the given workload, until they finish.
359
- * @param thread_aware_function The thread-aware function to be called for each thread index.
360
- * @throws If an exception occurs during execution of the thread-aware function.
361
- */
362
- template <typename thread_aware_function_at>
363
- void execute_bulk(thread_aware_function_at&& thread_aware_function) noexcept(false) {
364
- std::vector<std::thread> threads_pool;
365
- for (std::size_t thread_idx = 0; thread_idx != threads_count_; ++thread_idx)
366
- threads_pool.emplace_back([=]() { thread_aware_function(thread_idx); });
367
- for (std::size_t thread_idx = 0; thread_idx != threads_count_; ++thread_idx)
368
- threads_pool[thread_idx].join();
369
- }
370
- };
371
-
372
- #if USEARCH_USE_OPENMP
373
-
374
- /**
375
- * @brief An OpenMP-based executor or a "thread-pool" for parallel execution.
376
- * Is the preferred implementation, when available, and maximum performance is needed.
377
- */
378
- class executor_openmp_t {
379
- public:
380
- /**
381
- * @param threads_count The number of threads to be used for parallel execution.
382
- */
383
- executor_openmp_t(std::size_t threads_count = 0) noexcept {
384
- omp_set_num_threads(threads_count ? threads_count : std::thread::hardware_concurrency());
385
- }
386
-
387
- /**
388
- * @return Maximum number of threads available to the executor.
389
- */
390
- std::size_t size() const noexcept { return omp_get_num_threads(); }
391
-
392
- /**
393
- * @brief Executes tasks in bulk using the specified thread-aware function.
394
- * @param tasks The total number of tasks to be executed.
395
- * @param thread_aware_function The thread-aware function to be called for each thread index and task index.
396
- * @throws If an exception occurs during execution of the thread-aware function.
397
- */
398
- template <typename thread_aware_function_at>
399
- void execute_bulk(std::size_t tasks, thread_aware_function_at&& thread_aware_function) noexcept(false) {
400
- #pragma omp parallel for schedule(dynamic)
401
- for (std::size_t i = 0; i != tasks; ++i)
402
- thread_aware_function(omp_get_thread_num(), i);
403
- }
404
-
405
- /**
406
- * @brief Saturates every available thread with the given workload, until they finish.
407
- * @param thread_aware_function The thread-aware function to be called for each thread index.
408
- * @throws If an exception occurs during execution of the thread-aware function.
409
- */
410
- template <typename thread_aware_function_at>
411
- void execute_bulk(thread_aware_function_at&& thread_aware_function) noexcept(false) {
412
- #pragma omp parallel
413
- { thread_aware_function(omp_get_thread_num()); }
414
- }
415
- };
416
-
417
- using executor_default_t = executor_openmp_t;
418
-
419
- #else
420
-
421
- using executor_default_t = executor_stl_t;
422
-
423
- #endif
424
-
425
- /**
426
- * @brief Uses OS-specific APIs for aligned memory allocations.
427
- */
428
- template <typename element_at = char, std::size_t alignment_ak = 64> //
429
- class aligned_allocator_gt {
430
- public:
431
- using value_type = element_at;
432
- using size_type = std::size_t;
433
- using pointer = element_at*;
434
- using const_pointer = element_at const*;
435
- template <typename other_element_at> struct rebind {
436
- using other = aligned_allocator_gt<other_element_at>;
437
- };
438
-
439
- constexpr std::size_t alignment() const { return alignment_ak; }
440
-
441
- pointer allocate(size_type length) const {
442
- std::size_t length_bytes = alignment_ak * divide_round_up<alignment_ak>(length * sizeof(value_type));
443
- std::size_t alignment = alignment_ak;
444
- // void* result = nullptr;
445
- // int status = posix_memalign(&result, alignment, length_bytes);
446
- // return status == 0 ? (pointer)result : nullptr;
447
- #if defined(USEARCH_DEFINED_WINDOWS)
448
- return (pointer)_aligned_malloc(length_bytes, alignment);
449
- #else
450
- return (pointer)aligned_alloc(alignment, length_bytes);
451
- #endif
452
- }
453
-
454
- void deallocate(pointer begin, size_type) const {
455
- #if defined(USEARCH_DEFINED_WINDOWS)
456
- _aligned_free(begin);
457
- #else
458
- free(begin);
459
- #endif
460
- }
461
- };
462
-
463
- using aligned_allocator_t = aligned_allocator_gt<>;
464
-
465
- /**
466
- * @brief Memory-mapping allocator designed for "alloc many, free at once" usage patterns.
467
- * Thread-safe, @b except constructors and destructors.
468
- *
469
- * Using this memory allocator won't affect your overall speed much, as that is not the bottleneck.
470
- * However, it can drastically improve memory usage especcially for huge indexes of small vectors.
471
- */
472
- template <std::size_t alignment_ak = 1> class memory_mapping_allocator_gt {
473
-
474
- static constexpr std::size_t min_capacity() { return 1024 * 1024 * 4; }
475
- static constexpr std::size_t capacity_multiplier() { return 2; }
476
- static constexpr std::size_t head_size() {
477
- /// Pointer to the the previous arena and the size of the current one.
478
- return divide_round_up<alignment_ak>(sizeof(byte_t*) + sizeof(std::size_t)) * alignment_ak;
479
- }
480
-
481
- std::mutex mutex_;
482
- byte_t* last_arena_ = nullptr;
483
- std::size_t last_usage_ = head_size();
484
- std::size_t last_capacity_ = min_capacity();
485
- std::size_t wasted_space_ = 0;
486
-
487
- public:
488
- using value_type = byte_t;
489
- using size_type = std::size_t;
490
- using pointer = byte_t*;
491
- using const_pointer = byte_t const*;
492
-
493
- memory_mapping_allocator_gt() = default;
494
- memory_mapping_allocator_gt(memory_mapping_allocator_gt&& other) noexcept
495
- : last_arena_(exchange(other.last_arena_, nullptr)), last_usage_(exchange(other.last_usage_, 0)),
496
- last_capacity_(exchange(other.last_capacity_, 0)), wasted_space_(exchange(other.wasted_space_, 0)) {}
497
-
498
- memory_mapping_allocator_gt& operator=(memory_mapping_allocator_gt&& other) noexcept {
499
- std::swap(last_arena_, other.last_arena_);
500
- std::swap(last_usage_, other.last_usage_);
501
- std::swap(last_capacity_, other.last_capacity_);
502
- std::swap(wasted_space_, other.wasted_space_);
503
- return *this;
504
- }
505
-
506
- ~memory_mapping_allocator_gt() noexcept { reset(); }
507
-
508
- /**
509
- * @brief Discards all previously allocated memory buffers.
510
- */
511
- void reset() noexcept {
512
- byte_t* last_arena = last_arena_;
513
- while (last_arena) {
514
- byte_t* previous_arena;
515
- std::memcpy(&previous_arena, last_arena, sizeof(byte_t*));
516
- std::size_t current_size;
517
- std::memcpy(&current_size, last_arena + sizeof(byte_t*), sizeof(std::size_t));
518
- #if defined(USEARCH_DEFINED_WINDOWS)
519
- ::VirtualFree(last_arena, 0, MEM_RELEASE);
520
- #else
521
- munmap(last_arena, current_size);
522
- #endif
523
- last_arena = previous_arena;
524
- }
525
-
526
- // Clear the references:
527
- last_arena_ = nullptr;
528
- last_usage_ = head_size();
529
- last_capacity_ = min_capacity();
530
- wasted_space_ = 0;
531
- }
532
-
533
- /**
534
- * @brief Copy constructor.
535
- * @note This is a no-op copy constructor since the allocator is not copyable.
536
- */
537
- memory_mapping_allocator_gt(memory_mapping_allocator_gt const&) noexcept {}
538
-
539
- /**
540
- * @brief Copy assignment operator.
541
- * @note This is a no-op copy assignment operator since the allocator is not copyable.
542
- * @return Reference to the allocator after the assignment.
543
- */
544
- memory_mapping_allocator_gt& operator=(memory_mapping_allocator_gt const&) noexcept {
545
- reset();
546
- return *this;
547
- }
548
-
549
- /**
550
- * @brief Allocates an @b uninitialized block of memory of the specified size.
551
- * @param count_bytes The number of bytes to allocate.
552
- * @return A pointer to the allocated memory block, or `nullptr` if allocation fails.
553
- */
554
- inline byte_t* allocate(std::size_t count_bytes) noexcept {
555
- std::size_t extended_bytes = divide_round_up<alignment_ak>(count_bytes) * alignment_ak;
556
- // Check if the requested allocation size is absurd
557
- if (extended_bytes > min_capacity())
558
- return nullptr;
559
-
560
- std::unique_lock<std::mutex> lock(mutex_);
561
- if (!last_arena_ || (last_usage_ + extended_bytes > last_capacity_)) {
562
- std::size_t new_cap = last_capacity_ * capacity_multiplier();
563
- #if defined(USEARCH_DEFINED_WINDOWS)
564
- byte_t* new_arena = (byte_t*)(::VirtualAlloc(NULL, new_cap, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE));
565
- if (new_arena == nullptr)
566
- return nullptr;
567
- #else
568
- byte_t* new_arena = (byte_t*)mmap(NULL, new_cap, PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
569
- if (!new_arena)
570
- return nullptr;
571
- #endif
572
- std::memcpy(new_arena, &last_arena_, sizeof(byte_t*));
573
- std::memcpy(new_arena + sizeof(byte_t*), &new_cap, sizeof(std::size_t));
574
-
575
- wasted_space_ += total_reserved();
576
- last_arena_ = new_arena;
577
- last_capacity_ = new_cap;
578
- last_usage_ = head_size();
579
- }
580
-
581
- wasted_space_ += extended_bytes - count_bytes;
582
- return last_arena_ + exchange(last_usage_, last_usage_ + extended_bytes);
583
- }
584
-
585
- /**
586
- * @brief Returns the amount of memory used by the allocator across all arenas.
587
- * @return The amount of space in bytes.
588
- */
589
- std::size_t total_allocated() const noexcept {
590
- if (!last_arena_)
591
- return 0;
592
- std::size_t total_used = 0;
593
- std::size_t last_capacity = last_capacity_;
594
- do {
595
- total_used += last_capacity;
596
- last_capacity /= capacity_multiplier();
597
- } while (last_capacity >= min_capacity());
598
- return total_used;
599
- }
600
-
601
- /**
602
- * @brief Returns the amount of wasted space due to alignment.
603
- * @return The amount of wasted space in bytes.
604
- */
605
- std::size_t total_wasted() const noexcept { return wasted_space_; }
606
-
607
- /**
608
- * @brief Returns the amount of remaining memory already reserved but not yet used.
609
- * @return The amount of reserved memory in bytes.
610
- */
611
- std::size_t total_reserved() const noexcept { return last_capacity_ - last_usage_; }
612
-
613
- /**
614
- * @warning The very first memory de-allocation discards all the arenas!
615
- */
616
- void deallocate(byte_t* = nullptr, std::size_t = 0) noexcept { reset(); }
617
- };
618
-
619
- using memory_mapping_allocator_t = memory_mapping_allocator_gt<>;
620
-
621
- /**
622
- * @brief Utility class used to cast arrays of one scalar type to another,
623
- * avoiding unnecessary conversions.
624
- */
625
- template <typename from_scalar_at, typename to_scalar_at> struct cast_gt {
626
- inline bool operator()(byte_t const* input, std::size_t dimensions, byte_t* output) const {
627
- from_scalar_at const* typed_input = reinterpret_cast<from_scalar_at const*>(input);
628
- to_scalar_at* typed_output = reinterpret_cast<to_scalar_at*>(output);
629
- auto converter = [](from_scalar_at from) { return to_scalar_at(from); };
630
- std::transform(typed_input, typed_input + dimensions, typed_output, converter);
631
- return true;
632
- }
633
- };
634
-
635
- template <> struct cast_gt<f32_t, f32_t> {
636
- bool operator()(byte_t const*, std::size_t, byte_t*) const { return false; }
637
- };
638
-
639
- template <> struct cast_gt<f64_t, f64_t> {
640
- bool operator()(byte_t const*, std::size_t, byte_t*) const { return false; }
641
- };
642
-
643
- template <> struct cast_gt<f16_bits_t, f16_bits_t> {
644
- bool operator()(byte_t const*, std::size_t, byte_t*) const { return false; }
645
- };
646
-
647
- template <> struct cast_gt<f8_bits_t, f8_bits_t> {
648
- bool operator()(byte_t const*, std::size_t, byte_t*) const { return false; }
649
- };
650
-
651
- template <> struct cast_gt<b1x8_t, b1x8_t> {
652
- bool operator()(byte_t const*, std::size_t, byte_t*) const { return false; }
653
- };
654
-
655
- template <typename from_scalar_at> struct cast_gt<from_scalar_at, b1x8_t> {
656
- inline bool operator()(byte_t const* input, std::size_t dimensions, byte_t* output) const {
657
- from_scalar_at const* typed_input = reinterpret_cast<from_scalar_at const*>(input);
658
- unsigned char* typed_output = reinterpret_cast<unsigned char*>(output);
659
- for (std::size_t i = 0; i != dimensions; ++i)
660
- typed_output[i / CHAR_BIT] |= bool(typed_input[i]) ? (128 >> (i & (CHAR_BIT - 1))) : 0;
661
- return true;
662
- }
663
- };
664
-
665
- template <typename to_scalar_at> struct cast_gt<b1x8_t, to_scalar_at> {
666
- inline bool operator()(byte_t const* input, std::size_t dimensions, byte_t* output) const {
667
- unsigned char const* typed_input = reinterpret_cast<unsigned char const*>(input);
668
- to_scalar_at* typed_output = reinterpret_cast<to_scalar_at*>(output);
669
- for (std::size_t i = 0; i != dimensions; ++i)
670
- typed_output[i] = bool(typed_input[i / CHAR_BIT] & (128 >> (i & (CHAR_BIT - 1))));
671
- return true;
672
- }
673
- };
674
-
675
- } // namespace usearch
676
- } // namespace unum