usearch 2.15.3 → 2.16.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 (48) hide show
  1. package/README.md +1 -1
  2. package/cpp/test.cpp +16 -0
  3. package/include/usearch/index.hpp +21 -5
  4. package/include/usearch/index_dense.hpp +6 -1
  5. package/include/usearch/index_plugins.hpp +345 -8
  6. package/javascript/lib.cpp +17 -5
  7. package/javascript/usearch.test.js +190 -21
  8. package/package.json +1 -1
  9. package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
  10. package/prebuilds/linux-arm64/usearch.node +0 -0
  11. package/prebuilds/linux-x64/usearch.node +0 -0
  12. package/simsimd/.git-blame-ignore-revs +2 -0
  13. package/simsimd/CONTRIBUTING.md +71 -23
  14. package/simsimd/Cargo.lock +1 -1
  15. package/simsimd/Cargo.toml +4 -4
  16. package/simsimd/MANIFEST.in +2 -1
  17. package/simsimd/Package.swift +15 -3
  18. package/simsimd/README.md +104 -35
  19. package/simsimd/binding.gyp +6 -2
  20. package/simsimd/build.rs +2 -0
  21. package/simsimd/c/lib.c +180 -99
  22. package/simsimd/include/simsimd/binary.h +311 -102
  23. package/simsimd/include/simsimd/curved.h +134 -135
  24. package/simsimd/include/simsimd/dot.h +448 -274
  25. package/simsimd/include/simsimd/elementwise.h +1437 -0
  26. package/simsimd/include/simsimd/geospatial.h +2 -2
  27. package/simsimd/include/simsimd/mesh.h +2 -2
  28. package/simsimd/include/simsimd/probability.h +88 -88
  29. package/simsimd/include/simsimd/simsimd.h +1157 -772
  30. package/simsimd/include/simsimd/sparse.h +768 -147
  31. package/simsimd/include/simsimd/spatial.h +955 -376
  32. package/simsimd/include/simsimd/types.h +145 -71
  33. package/simsimd/javascript/lib.c +1 -2
  34. package/simsimd/package.json +4 -4
  35. package/simsimd/rust/lib.rs +14 -1
  36. package/simsimd/scripts/bench.cxx +1070 -0
  37. package/simsimd/scripts/bench.ipynb +187 -0
  38. package/simsimd/{javascript → scripts}/bench.js +1 -1
  39. package/simsimd/{rust/benches/sqeuclidean.rs → scripts/bench_sqeuclidean.rs} +6 -6
  40. package/simsimd/scripts/bench_vectors.py +700 -0
  41. package/simsimd/scripts/bench_vectors_live.py +213 -0
  42. package/simsimd/scripts/test.c +155 -0
  43. package/simsimd/{javascript → scripts}/test.mjs +2 -2
  44. package/simsimd/scripts/test.py +1410 -0
  45. package/simsimd/swift/{source/SimSIMD.swift → SimSIMD.swift} +6 -2
  46. package/simsimd/swift/{tests/SimSIMDTests.swift → Test.swift} +1 -1
  47. /package/simsimd/{rust/benches/cosine.rs → scripts/bench_cosine.rs} +0 -0
  48. /package/simsimd/{rust/benches/native.rs → scripts/bench_native.rs} +0 -0
@@ -0,0 +1,1070 @@
1
+ #include <cmath> // `std::sqrt`
2
+ #include <cstdlib> // `std::aligned_alloc`
3
+ #include <cstring> // `std::memcpy`
4
+ #include <random> // `std::uniform_int_distribution`
5
+ #include <thread> // `std::thread`
6
+ #include <tuple> // `std::tuple` for callable introspection
7
+ #include <type_traits> // ``
8
+ #include <unordered_set> // `std::unordered_set`
9
+ #include <vector> // `std::vector`
10
+
11
+ #include <benchmark/benchmark.h>
12
+
13
+ #if !defined(SIMSIMD_BUILD_BENCHMARKS_WITH_CBLAS)
14
+ #define SIMSIMD_BUILD_BENCHMARKS_WITH_CBLAS 0
15
+ #endif
16
+ #if SIMSIMD_BUILD_BENCHMARKS_WITH_CBLAS
17
+ #include <cblas.h>
18
+ #endif
19
+
20
+ // It's important to note, that out compression/decompression routines
21
+ // are quite inaccurate. They are not meant to be used in production code.
22
+ // So when benchmarking, if possible, please use the native types, if those
23
+ // are implemented.
24
+ #define SIMSIMD_NATIVE_F16 1
25
+ #define SIMSIMD_NATIVE_BF16 1
26
+ #include <simsimd/simsimd.h>
27
+
28
+ constexpr std::size_t default_seconds = 10;
29
+ constexpr std::size_t default_threads = 1;
30
+ constexpr simsimd_distance_t signaling_distance = std::numeric_limits<simsimd_distance_t>::signaling_NaN();
31
+
32
+ /// Matches OpenAI embedding size
33
+ /// For sub-byte data types
34
+ constexpr std::size_t dense_dimensions = 1536;
35
+ /// Has quadratic impact on the number of operations
36
+ constexpr std::size_t curved_dimensions = 128;
37
+
38
+ namespace bm = benchmark;
39
+
40
+ // clang-format off
41
+ template <simsimd_datatype_t> struct datatype_enum_to_type_gt { using value_t = void; };
42
+ template <> struct datatype_enum_to_type_gt<simsimd_datatype_f64_k> { using value_t = simsimd_f64_t; };
43
+ template <> struct datatype_enum_to_type_gt<simsimd_datatype_f32_k> { using value_t = simsimd_f32_t; };
44
+ template <> struct datatype_enum_to_type_gt<simsimd_datatype_f16_k> { using value_t = simsimd_f16_t; };
45
+ template <> struct datatype_enum_to_type_gt<simsimd_datatype_bf16_k> { using value_t = simsimd_bf16_t; };
46
+ template <> struct datatype_enum_to_type_gt<simsimd_datatype_f64c_k> { using value_t = simsimd_f64_t; };
47
+ template <> struct datatype_enum_to_type_gt<simsimd_datatype_f32c_k> { using value_t = simsimd_f32_t; };
48
+ template <> struct datatype_enum_to_type_gt<simsimd_datatype_f16c_k> { using value_t = simsimd_f16_t; };
49
+ template <> struct datatype_enum_to_type_gt<simsimd_datatype_bf16c_k> { using value_t = simsimd_bf16_t; };
50
+ template <> struct datatype_enum_to_type_gt<simsimd_datatype_b8_k> { using value_t = simsimd_b8_t; };
51
+ template <> struct datatype_enum_to_type_gt<simsimd_datatype_i8_k> { using value_t = simsimd_i8_t; };
52
+ template <> struct datatype_enum_to_type_gt<simsimd_datatype_u8_k> { using value_t = simsimd_u8_t; };
53
+ template <> struct datatype_enum_to_type_gt<simsimd_datatype_i16_k> { using value_t = simsimd_i16_t; };
54
+ template <> struct datatype_enum_to_type_gt<simsimd_datatype_u16_k> { using value_t = simsimd_u16_t; };
55
+ template <> struct datatype_enum_to_type_gt<simsimd_datatype_i32_k> { using value_t = simsimd_i32_t; };
56
+ template <> struct datatype_enum_to_type_gt<simsimd_datatype_u32_k> { using value_t = simsimd_u32_t; };
57
+ template <> struct datatype_enum_to_type_gt<simsimd_datatype_i64_k> { using value_t = simsimd_i64_t; };
58
+ template <> struct datatype_enum_to_type_gt<simsimd_datatype_u64_k> { using value_t = simsimd_u64_t; };
59
+ // clang-format on
60
+
61
+ template <std::size_t multiple>
62
+ std::size_t divide_round_up(std::size_t n) {
63
+ return ((n + multiple - 1) / multiple) * multiple;
64
+ }
65
+
66
+ /**
67
+ * @brief Vector-like fixed capacity buffer, ensuring cache-line alignment.
68
+ * @tparam datatype_ak The data type of the vector elements, represented as a `simsimd_datatype_t`.
69
+ */
70
+ template <simsimd_datatype_t datatype_ak>
71
+ struct vector_gt {
72
+ using scalar_t = typename datatype_enum_to_type_gt<datatype_ak>::value_t;
73
+ using compressed16_t = unsigned short;
74
+ static constexpr bool is_integral =
75
+ datatype_ak == datatype_ak == simsimd_datatype_b8_k || //
76
+ datatype_ak == simsimd_datatype_i8_k || datatype_ak == simsimd_datatype_u8_k || //
77
+ datatype_ak == simsimd_datatype_i16_k || datatype_ak == simsimd_datatype_u16_k || //
78
+ datatype_ak == simsimd_datatype_i32_k || datatype_ak == simsimd_datatype_u32_k ||
79
+ datatype_ak == simsimd_datatype_i64_k || datatype_ak == simsimd_datatype_u64_k;
80
+ static constexpr std::size_t cacheline_length = 64;
81
+
82
+ scalar_t *buffer_ = nullptr;
83
+ std::size_t dimensions_ = 0;
84
+
85
+ vector_gt() = default;
86
+ vector_gt(std::size_t dimensions) noexcept(false)
87
+ : dimensions_(dimensions),
88
+ buffer_(static_cast<scalar_t *>(
89
+ std::aligned_alloc(cacheline_length, divide_round_up<cacheline_length>(dimensions * sizeof(scalar_t))))) {
90
+ if (!buffer_) throw std::bad_alloc();
91
+ }
92
+
93
+ ~vector_gt() noexcept { std::free(buffer_); }
94
+
95
+ vector_gt(vector_gt const &other) : vector_gt(other.dimensions()) {
96
+ std::memcpy(buffer_, other.buffer_, divide_round_up<cacheline_length>(dimensions_ * sizeof(scalar_t)));
97
+ }
98
+ vector_gt &operator=(vector_gt const &other) {
99
+ if (this != &other) {
100
+ if (dimensions_ != other.dimensions()) {
101
+ std::free(buffer_);
102
+ dimensions_ = other.dimensions();
103
+ buffer_ = static_cast<scalar_t *>(std::aligned_alloc(
104
+ cacheline_length, divide_round_up<cacheline_length>(dimensions_ * sizeof(scalar_t))));
105
+ if (!buffer_) throw std::bad_alloc();
106
+ }
107
+ std::memcpy(buffer_, other.buffer_, divide_round_up<cacheline_length>(dimensions_ * sizeof(scalar_t)));
108
+ }
109
+ return *this;
110
+ }
111
+
112
+ std::size_t dimensions() const noexcept { return dimensions_; }
113
+ std::size_t size_bytes() const noexcept {
114
+ return divide_round_up<cacheline_length>(dimensions_ * sizeof(scalar_t));
115
+ }
116
+ scalar_t *data() noexcept { return buffer_; }
117
+ scalar_t const *data() const noexcept { return buffer_; }
118
+
119
+ /**
120
+ * @brief Broadcast a scalar value to all elements of the vector.
121
+ * @param v The scalar value to broadcast.
122
+ */
123
+ void set(scalar_t v) noexcept {
124
+ for (std::size_t i = 0; i != dimensions_; ++i) buffer_[i] = v;
125
+ }
126
+
127
+ /**
128
+ * @brief Compresses a double value into the vector's scalar type.
129
+ * @param from The double value to compress.
130
+ * @param to The scalar type where the compressed value will be stored.
131
+ */
132
+ static void compress(double const &from, scalar_t &to) noexcept {
133
+ // In a NaN, the sign bit is irrelevant, mantissa describes the kind of NaN,
134
+ // and the exponent is all ones - we can only check the the exponent bits.
135
+ // Brain float is similar: https://en.wikipedia.org/wiki/Bfloat16_floating-point_format
136
+ constexpr unsigned short exponent_mask_f16 = 0b0111110000000000; // 1 sign, 5 exp, 10 mantissa
137
+ constexpr unsigned short exponent_mask_bf16 = 0b0111111110000000; // 1 sign, 8 exp, 7 mantissa
138
+ constexpr unsigned int exponent_mask_f32 = 0b01111111100000000000000000000000; // 1 sign, 8 exp, 23 mantissa
139
+ constexpr unsigned long long exponent_mask_f64 = // 1 sign, 11 exp, 52 mantissa
140
+ 0b011111111110000000000000000000000000000000000000000000000000000;
141
+
142
+ #if !SIMSIMD_NATIVE_BF16
143
+ if constexpr (datatype_ak == simsimd_datatype_bf16_k || datatype_ak == simsimd_datatype_bf16c_k) {
144
+ simsimd_f32_to_bf16(from, &to);
145
+ if ((to & exponent_mask_bf16) == exponent_mask_bf16) to = 0;
146
+ static_assert(sizeof(scalar_t) == sizeof(simsimd_bf16_t));
147
+ return;
148
+ }
149
+ #endif
150
+ #if !SIMSIMD_NATIVE_F16
151
+ if constexpr (datatype_ak == simsimd_datatype_f16_k || datatype_ak == simsimd_datatype_f16c_k) {
152
+ simsimd_f32_to_f16(from, &to);
153
+ if ((to & exponent_mask_f16) == exponent_mask_f16) to = 0;
154
+ static_assert(sizeof(scalar_t) == sizeof(simsimd_f16_t));
155
+ return;
156
+ }
157
+ #endif
158
+ to = static_cast<scalar_t>(from);
159
+ }
160
+
161
+ /**
162
+ * @brief Decompresses the vector's scalar type into a double value.
163
+ * @param from The compressed scalar value to decompress.
164
+ * @return The decompressed double value.
165
+ */
166
+ static double uncompress(scalar_t const &from) noexcept {
167
+ #if !SIMSIMD_NATIVE_BF16
168
+ if constexpr (datatype_ak == simsimd_datatype_bf16_k || datatype_ak == simsimd_datatype_bf16c_k) {
169
+ return simsimd_bf16_to_f32((simsimd_bf16_t const *)&from);
170
+ }
171
+ #endif
172
+ #if !SIMSIMD_NATIVE_F16
173
+ if constexpr (datatype_ak == simsimd_datatype_f16_k || datatype_ak == simsimd_datatype_f16c_k) {
174
+ return simsimd_f16_to_f32((simsimd_f16_t const *)&from);
175
+ }
176
+ #endif
177
+ return from;
178
+ }
179
+
180
+ /**
181
+ * @brief Randomizes the vector elements with normalized values.
182
+ *
183
+ * This method fills the vector with random values. For floating-point types, the vector is normalized
184
+ * so that the sum of the squares of its elements equals 1. For integral types, the values are generated
185
+ * within the range of the scalar type.
186
+ */
187
+ void randomize(std::uint32_t seed) noexcept {
188
+
189
+ static std::mt19937 generator;
190
+ generator.seed(seed);
191
+
192
+ if constexpr (is_integral) {
193
+ std::uniform_int_distribution<scalar_t> distribution(std::numeric_limits<scalar_t>::min(),
194
+ std::numeric_limits<scalar_t>::max());
195
+ for (std::size_t i = 0; i != dimensions_; ++i) { buffer_[i] = distribution(generator); }
196
+ }
197
+ else {
198
+ // Using non-uniform distribution helps detect tail errors
199
+ std::normal_distribution<double> distribution(0.1, 1.0);
200
+ double squared_sum = 0.0;
201
+ for (std::size_t i = 0; i != dimensions_; ++i) {
202
+ double a_i = distribution(generator);
203
+ squared_sum += a_i * a_i;
204
+ compress(a_i, buffer_[i]);
205
+ }
206
+
207
+ // Normalize the vectors:
208
+ squared_sum = std::sqrt(squared_sum);
209
+ for (std::size_t i = 0; i != dimensions_; ++i) {
210
+ compress(uncompress(buffer_[i]) / squared_sum, buffer_[i]);
211
+ // Zero out NaNs
212
+ if (std::isnan(uncompress(buffer_[i]))) buffer_[i] = 0;
213
+ }
214
+ }
215
+ }
216
+ };
217
+
218
+ template <simsimd_datatype_t datatype_ak>
219
+ struct vectors_pair_gt {
220
+ using vector_t = vector_gt<datatype_ak>;
221
+ using scalar_t = typename vector_t::scalar_t;
222
+ static constexpr bool is_integral = vector_t::is_integral;
223
+
224
+ vector_t a;
225
+ vector_t b;
226
+
227
+ vectors_pair_gt() noexcept = default;
228
+ vectors_pair_gt(std::size_t dimensions) noexcept : a(dimensions), b(dimensions) {}
229
+ vectors_pair_gt(std::size_t dimensions_a, std::size_t dimensions_b) noexcept : a(dimensions_a), b(dimensions_b) {}
230
+ vectors_pair_gt(vectors_pair_gt const &other) noexcept(false) : a(other.a), b(other.b) {}
231
+ vectors_pair_gt &operator=(vectors_pair_gt const &other) noexcept(false) {
232
+ if (this != &other) a = other.a, b = other.b;
233
+ return *this;
234
+ }
235
+ };
236
+
237
+ /**
238
+ * @brief Measures the performance of a @b dense metric function against a baseline using Google Benchmark.
239
+ * @tparam pair_at The type representing the vector pair used in the measurement.
240
+ * @tparam metric_at The type of the metric function (default is void).
241
+ * @param state The benchmark state object provided by Google Benchmark.
242
+ * @param metric The metric function to benchmark.
243
+ * @param baseline The baseline function to compare against.
244
+ * @param dimensions The number of dimensions in the vectors.
245
+ */
246
+ template <typename pair_at, typename metric_at = void>
247
+ void measure_dense(bm::State &state, metric_at metric, metric_at baseline, std::size_t dimensions) {
248
+
249
+ using pair_t = pair_at;
250
+ using vector_t = typename pair_at::vector_t;
251
+
252
+ auto call_baseline = [&](pair_t &pair) -> double {
253
+ // Output for real vectors have a single dimensions.
254
+ // Output for complex vectors have two dimensions.
255
+ simsimd_distance_t results[2] = {signaling_distance, signaling_distance};
256
+ baseline(pair.a.data(), pair.b.data(), pair.a.dimensions(), &results[0]);
257
+ return results[0];
258
+ };
259
+ auto call_contender = [&](pair_t &pair) -> double {
260
+ // Output for real vectors have a single dimensions.
261
+ // Output for complex vectors have two dimensions.
262
+ simsimd_distance_t results[2] = {signaling_distance, signaling_distance};
263
+ metric(pair.a.data(), pair.b.data(), pair.a.dimensions(), &results[0]);
264
+ return results[0];
265
+ };
266
+
267
+ // Let's average the distance results over many pairs.
268
+ constexpr std::size_t pairs_count = 128;
269
+ std::vector<pair_t> pairs(pairs_count);
270
+ for (std::size_t i = 0; i != pairs.size(); ++i) {
271
+ auto &pair = pairs[i];
272
+ pair.a = pair.b = vector_t(dimensions);
273
+ pair.a.randomize(static_cast<std::uint32_t>(i)), pair.b.randomize(static_cast<std::uint32_t>(i) + 54321u);
274
+ }
275
+
276
+ // Initialize the output buffers for distance calculations.
277
+ std::vector<double> results_baseline(pairs.size());
278
+ std::vector<double> results_contender(pairs.size());
279
+ for (std::size_t i = 0; i != pairs.size(); ++i)
280
+ results_baseline[i] = call_baseline(pairs[i]), results_contender[i] = call_contender(pairs[i]);
281
+
282
+ // The actual benchmarking loop.
283
+ std::size_t iterations = 0;
284
+ for (auto _ : state)
285
+ bm::DoNotOptimize((results_contender[iterations & (pairs_count - 1)] =
286
+ call_contender(pairs[iterations & (pairs_count - 1)]))),
287
+ iterations++;
288
+
289
+ // Measure the mean absolute delta and relative error.
290
+ double mean_delta = 0, mean_relative_error = 0;
291
+ for (std::size_t i = 0; i != pairs.size(); ++i) {
292
+ auto abs_delta = std::abs(results_contender[i] - results_baseline[i]);
293
+ mean_delta += abs_delta;
294
+ double error = abs_delta != 0 && results_baseline[i] != 0 ? abs_delta / std::abs(results_baseline[i]) : 0;
295
+ mean_relative_error += error;
296
+ }
297
+ mean_delta /= pairs.size();
298
+ mean_relative_error /= pairs.size();
299
+ state.counters["abs_delta"] = mean_delta;
300
+ state.counters["relative_error"] = mean_relative_error;
301
+ state.counters["bytes"] = bm::Counter(iterations * pairs[0].a.size_bytes() * 2, bm::Counter::kIsRate);
302
+ state.counters["pairs"] = bm::Counter(iterations, bm::Counter::kIsRate);
303
+ }
304
+
305
+ /**
306
+ * @brief Measures the performance of a @b curved metric function against a baseline using Google Benchmark.
307
+ * @tparam pair_at The type representing the vector pair used in the measurement.
308
+ * @tparam metric_at The type of the metric function (default is void).
309
+ * @param state The benchmark state object provided by Google Benchmark.
310
+ * @param metric The metric function to benchmark.
311
+ * @param baseline The baseline function to compare against.
312
+ * @param dimensions The number of dimensions in the vectors.
313
+ */
314
+ template <typename pair_at, typename metric_at = void>
315
+ void measure_curved(bm::State &state, metric_at metric, metric_at baseline, std::size_t dimensions) {
316
+
317
+ using pair_t = pair_at;
318
+ using vector_t = typename pair_at::vector_t;
319
+
320
+ auto call_baseline = [&](pair_t const &pair, vector_t const &tensor) -> double {
321
+ simsimd_distance_t result = signaling_distance;
322
+ baseline(pair.a.data(), pair.b.data(), tensor.data(), pair.a.dimensions(), &result);
323
+ return result;
324
+ };
325
+ auto call_contender = [&](pair_t const &pair, vector_t const &tensor) -> double {
326
+ simsimd_distance_t result = signaling_distance;
327
+ metric(pair.a.data(), pair.b.data(), tensor.data(), pair.a.dimensions(), &result);
328
+ return result;
329
+ };
330
+
331
+ // Let's average the distance results over many pairs.
332
+ constexpr std::size_t pairs_count = 128;
333
+ std::vector<pair_t> pairs(pairs_count);
334
+ std::vector<vector_t> tensors(pairs_count);
335
+ for (std::size_t i = 0; i != pairs.size(); ++i) {
336
+ pair_t &pair = pairs[i];
337
+ pair.a = pair.b = vector_t(dimensions);
338
+ pair.a.randomize(static_cast<std::uint32_t>(i)), pair.b.randomize(static_cast<std::uint32_t>(i) + 54321u);
339
+ vector_t &tensor = tensors[i];
340
+ tensor = vector_t(dimensions * dimensions);
341
+ tensor.randomize(static_cast<std::uint32_t>(i) + 123456u);
342
+ }
343
+
344
+ // Initialize the output buffers for distance calculations.
345
+ std::vector<double> results_baseline(pairs.size());
346
+ std::vector<double> results_contender(pairs.size());
347
+ for (std::size_t i = 0; i != pairs.size(); ++i)
348
+ results_baseline[i] = call_baseline(pairs[i], tensors[i]),
349
+ results_contender[i] = call_contender(pairs[i], tensors[i]);
350
+
351
+ // The actual benchmarking loop.
352
+ std::size_t iterations = 0;
353
+ for (auto _ : state)
354
+ bm::DoNotOptimize((results_contender[iterations & (pairs_count - 1)] = call_contender(
355
+ pairs[iterations & (pairs_count - 1)], tensors[iterations & (pairs_count - 1)]))),
356
+ iterations++;
357
+
358
+ // Measure the mean absolute delta and relative error.
359
+ double mean_delta = 0, mean_relative_error = 0;
360
+ for (std::size_t i = 0; i != pairs.size(); ++i) {
361
+ auto abs_delta = std::abs(results_contender[i] - results_baseline[i]);
362
+ mean_delta += abs_delta;
363
+ double error = abs_delta != 0 && results_baseline[i] != 0 ? abs_delta / std::abs(results_baseline[i]) : 0;
364
+ mean_relative_error += error;
365
+ }
366
+ mean_delta /= pairs.size();
367
+ mean_relative_error /= pairs.size();
368
+ state.counters["abs_delta"] = mean_delta;
369
+ state.counters["relative_error"] = mean_relative_error;
370
+ state.counters["bytes"] = bm::Counter(iterations * pairs[0].a.size_bytes() * 2, bm::Counter::kIsRate);
371
+ state.counters["pairs"] = bm::Counter(iterations, bm::Counter::kIsRate);
372
+ }
373
+
374
+ /**
375
+ * @brief Measures the performance of a @b sparse metric function against a baseline using Google Benchmark.
376
+ * @tparam pair_at The type representing the vector pair used in the measurement.
377
+ * @tparam metric_at The type of the metric function (default is void).
378
+ * @param state The benchmark state object provided by Google Benchmark.
379
+ * @param metric The metric function to benchmark.
380
+ * @param baseline The baseline function to compare against.
381
+ * @param dimensions_a The number of dimensions in the smaller vector.
382
+ * @param dimensions_b The number of dimensions in the larger vector.
383
+ * @param intersection_size The expected number of common scalars between the vectors.
384
+ */
385
+ template <typename pair_at, typename metric_at = void>
386
+ void measure_sparse(bm::State &state, metric_at metric, metric_at baseline, std::size_t dimensions_a,
387
+ std::size_t dimensions_b, std::size_t intersection_size) {
388
+
389
+ using pair_t = pair_at;
390
+ using vector_t = typename pair_at::vector_t;
391
+ using scalar_t = typename vector_t::scalar_t;
392
+
393
+ auto call_baseline = [&](pair_t &pair) -> double {
394
+ simsimd_distance_t result = std::numeric_limits<simsimd_distance_t>::signaling_NaN();
395
+ baseline(pair.a.data(), pair.b.data(), pair.a.dimensions(), pair.b.dimensions(), &result);
396
+ return result;
397
+ };
398
+ auto call_contender = [&](pair_t &pair) -> double {
399
+ simsimd_distance_t result = std::numeric_limits<simsimd_distance_t>::signaling_NaN();
400
+ metric(pair.a.data(), pair.b.data(), pair.a.dimensions(), pair.b.dimensions(), &result);
401
+ return result;
402
+ };
403
+
404
+ // Let's average the distance results over many pairs.
405
+ constexpr std::size_t pairs_count = 128;
406
+ std::vector<pair_t> pairs(pairs_count);
407
+ std::random_device seed_source;
408
+ std::mt19937 generator(seed_source());
409
+ std::uniform_int_distribution<scalar_t> distribution(0, std::numeric_limits<scalar_t>::max());
410
+
411
+ // Randomizing the vectors for sparse distances is a bit more complex then:
412
+ //
413
+ // pair.a.randomize(), pair.b.randomize();
414
+ //
415
+ // We need to ensure that the intersection is of the expected size.
416
+ std::unordered_set<scalar_t> intersection_set, unique_a, unique_b;
417
+ intersection_set.reserve(intersection_size);
418
+ unique_a.reserve(dimensions_a - intersection_size);
419
+ unique_b.reserve(dimensions_b - intersection_size);
420
+
421
+ for (auto &pair : pairs) {
422
+ pair.a = vector_t(dimensions_a);
423
+ pair.b = vector_t(dimensions_b);
424
+
425
+ // Step 1: Generate intersection set
426
+ intersection_set.clear();
427
+ while (intersection_set.size() < intersection_size) intersection_set.insert(distribution(generator));
428
+
429
+ unique_a.clear();
430
+ while (unique_a.size() < dimensions_a - intersection_size) {
431
+ scalar_t element = distribution(generator);
432
+ if (intersection_set.find(element) == intersection_set.end()) unique_a.insert(element);
433
+ }
434
+
435
+ unique_b.clear();
436
+ while (unique_b.size() < dimensions_b - intersection_size) {
437
+ scalar_t element = distribution(generator);
438
+ if (intersection_set.find(element) == intersection_set.end() && unique_a.find(element) == unique_a.end())
439
+ unique_b.insert(element);
440
+ }
441
+
442
+ // Step 2: Merge and sort
443
+ std::copy(intersection_set.begin(), intersection_set.end(), pair.a.buffer_);
444
+ std::copy(intersection_set.begin(), intersection_set.end(), pair.b.buffer_);
445
+ std::copy(unique_a.begin(), unique_a.end(), pair.a.buffer_ + intersection_size);
446
+ std::copy(unique_b.begin(), unique_b.end(), pair.b.buffer_ + intersection_size);
447
+ std::sort(pair.a.buffer_, pair.a.buffer_ + dimensions_a);
448
+ std::sort(pair.b.buffer_, pair.b.buffer_ + dimensions_b);
449
+ }
450
+
451
+ // Initialize the output buffers for distance calculations.
452
+ std::vector<double> results_baseline(pairs.size());
453
+ std::vector<double> results_contender(pairs.size());
454
+ for (std::size_t i = 0; i != pairs.size(); ++i)
455
+ results_baseline[i] = call_baseline(pairs[i]), results_contender[i] = call_contender(pairs[i]);
456
+
457
+ // The actual benchmarking loop.
458
+ std::size_t iterations = 0;
459
+ for (auto _ : state)
460
+ bm::DoNotOptimize((results_contender[iterations & (pairs_count - 1)] =
461
+ call_contender(pairs[iterations & (pairs_count - 1)]))),
462
+ iterations++;
463
+
464
+ // Measure the mean absolute delta and relative error.
465
+ double mean_error = 0;
466
+ for (std::size_t i = 0; i != pairs.size(); ++i) {
467
+ auto abs_error = std::abs(results_contender[i] - results_baseline[i]);
468
+ mean_error += abs_error;
469
+ }
470
+ mean_error /= pairs.size();
471
+ state.counters["error"] = mean_error;
472
+ state.counters["bytes"] =
473
+ bm::Counter(iterations * (pairs[0].a.size_bytes() + pairs[0].b.size_bytes()), bm::Counter::kIsRate);
474
+ state.counters["pairs"] = bm::Counter(iterations, bm::Counter::kIsRate);
475
+ state.counters["matches"] =
476
+ std::accumulate(results_contender.begin(), results_contender.end(), 0.0) / results_contender.size();
477
+ }
478
+
479
+ template <typename... function_args_at>
480
+ constexpr std::size_t function_args_count(void (*function)(function_args_at...)) {
481
+ return sizeof...(function_args_at);
482
+ }
483
+
484
+ /**
485
+ * @brief Measures the performance of a vector-vector @b FMA function against a baseline using Google Benchmark.
486
+ * @tparam pair_at The type representing the vector pair used in the measurement.
487
+ * @tparam kernel_at The type of the kernel function (default is void).
488
+ * @param state The benchmark state object provided by Google Benchmark.
489
+ * @param kernel The kernel function to benchmark.
490
+ * @param baseline The baseline function to compare against.
491
+ * @param dimensions The number of dimensions in the vectors.
492
+ */
493
+ template <typename pair_at, typename kernel_at = void, typename l2_metric_at = void>
494
+ void measure_fma(bm::State &state, kernel_at kernel, kernel_at baseline, l2_metric_at l2_metric,
495
+ std::size_t dimensions) {
496
+
497
+ using pair_t = pair_at;
498
+ using vector_t = typename pair_at::vector_t;
499
+
500
+ constexpr simsimd_distance_t alpha = 0.2;
501
+ constexpr simsimd_distance_t beta = 0.3;
502
+ static_assert(function_args_count(kernel_at {}) >= 6 && function_args_count(kernel_at {}) <= 7,
503
+ "Kernel must take two or three vectors.");
504
+
505
+ auto call_baseline = [&](vector_t const &a, vector_t const &b, vector_t const &c, vector_t &d) {
506
+ if constexpr (function_args_count(kernel_at {}) == 6) {
507
+ baseline(a.data(), c.data(), a.dimensions(), alpha, beta, d.data());
508
+ }
509
+ else { baseline(a.data(), b.data(), c.data(), a.dimensions(), alpha, beta, d.data()); }
510
+ };
511
+ auto call_contender = [&](vector_t const &a, vector_t const &b, vector_t const &c, vector_t &d) {
512
+ if constexpr (function_args_count(kernel_at {}) == 6) {
513
+ kernel(a.data(), c.data(), a.dimensions(), alpha, beta, d.data());
514
+ }
515
+ else { kernel(a.data(), b.data(), c.data(), a.dimensions(), alpha, beta, d.data()); }
516
+ };
517
+
518
+ // Let's average the distance results over many quads.
519
+ struct quad_t {
520
+ vector_t a, b, c, d;
521
+ };
522
+ constexpr std::size_t quads_count = 128;
523
+ std::vector<quad_t> quads(quads_count);
524
+ for (std::size_t i = 0; i != quads.size(); ++i) {
525
+ auto &quad = quads[i];
526
+ quad.a = quad.b = quad.c = quad.d = vector_t(dimensions);
527
+ quad.a.randomize(static_cast<std::uint32_t>(i));
528
+ quad.b.set(2); // Having a small constant here will help avoid overflows
529
+ quad.c.randomize(static_cast<std::uint32_t>(i) + 54321u);
530
+ }
531
+
532
+ // Initialize the output buffers for distance calculations.
533
+ vector_t baseline_d(dimensions), contender_d(dimensions), zeros(dimensions);
534
+ std::vector<simsimd_distance_t> l2_metric_from_baseline(quads.size());
535
+ std::vector<simsimd_distance_t> l2_baseline_result_norm(quads.size());
536
+ std::vector<simsimd_distance_t> l2_contender_result_norm(quads.size());
537
+ zeros.set(0);
538
+ double mean_delta = 0, mean_relative_error = 0;
539
+ for (std::size_t i = 0; i != quads.size(); ++i) {
540
+ quad_t &quad = quads[i];
541
+ call_baseline(quad.a, quad.b, quad.c, baseline_d);
542
+ call_contender(quad.a, quad.b, quad.c, contender_d);
543
+ l2_metric(baseline_d.data(), contender_d.data(), dimensions, &l2_metric_from_baseline[i]);
544
+ l2_metric(baseline_d.data(), zeros.data(), dimensions, &l2_baseline_result_norm[i]);
545
+ l2_metric(contender_d.data(), zeros.data(), dimensions, &l2_contender_result_norm[i]);
546
+
547
+ mean_delta += std::abs(l2_metric_from_baseline[i]);
548
+ mean_relative_error +=
549
+ std::abs(l2_metric_from_baseline[i]) / (std::max)(l2_baseline_result_norm[i], l2_contender_result_norm[i]);
550
+ }
551
+ mean_delta /= quads_count;
552
+ mean_relative_error /= quads_count;
553
+
554
+ // The actual benchmarking loop.
555
+ std::size_t iterations = 0;
556
+ for (auto _ : state) {
557
+ quad_t &quad = quads[iterations & (quads_count - 1)];
558
+ call_contender(quad.a, quad.b, quad.c, quad.d);
559
+ iterations++;
560
+ }
561
+
562
+ // Measure the mean absolute delta and relative error.
563
+ state.counters["abs_delta"] = mean_delta;
564
+ state.counters["relative_error"] = mean_relative_error;
565
+ state.counters["bytes"] = bm::Counter(
566
+ iterations * quads[0].a.size_bytes() * (function_args_count(kernel_at {}) > 6 ? 3 : 2), bm::Counter::kIsRate);
567
+ state.counters["pairs"] = bm::Counter(iterations, bm::Counter::kIsRate);
568
+ }
569
+
570
+ template <simsimd_datatype_t datatype_ak, typename metric_at = void>
571
+ void dense_(std::string name, metric_at *distance_func, metric_at *baseline_func) {
572
+ using pair_t = vectors_pair_gt<datatype_ak>;
573
+ std::string bench_name = name + "<" + std::to_string(dense_dimensions) + "d>";
574
+ bm::RegisterBenchmark(bench_name.c_str(), measure_dense<pair_t, metric_at *>, distance_func, baseline_func,
575
+ dense_dimensions)
576
+ ->MinTime(default_seconds)
577
+ ->Threads(default_threads);
578
+ }
579
+
580
+ template <simsimd_datatype_t datatype_ak, typename kernel_at = void, typename l2_metric_at = void>
581
+ void fma_(std::string name, kernel_at *kernel_func, kernel_at *baseline_func, l2_metric_at *l2_metric_func) {
582
+ using pair_t = vectors_pair_gt<datatype_ak>;
583
+ std::string bench_name = name + "<" + std::to_string(dense_dimensions) + "d>";
584
+ bm::RegisterBenchmark(bench_name.c_str(), measure_fma<pair_t, kernel_at *, l2_metric_at *>, kernel_func,
585
+ baseline_func, l2_metric_func, dense_dimensions)
586
+ ->MinTime(default_seconds)
587
+ ->Threads(default_threads);
588
+ }
589
+
590
+ template <simsimd_datatype_t datatype_ak, typename metric_at = void>
591
+ void sparse_(std::string name, metric_at *distance_func, metric_at *baseline_func) {
592
+
593
+ using pair_t = vectors_pair_gt<datatype_ak>;
594
+
595
+ // Register different lengths, intersection sizes, and distributions
596
+ // 2 first lengths * 3 second length multipliers * 4 intersection grades = 24 benchmarks for each metric.
597
+ for (std::size_t first_len : {128, 1024}) { //< 2 lengths
598
+ for (std::size_t second_len_multiplier : {1, 8, 64}) { //< 3 length multipliers
599
+ for (double intersection_share : {0.01, 0.05, 0.5, 0.95}) { //< 4 intersection grades
600
+ std::size_t intersection_size = static_cast<std::size_t>(first_len * intersection_share);
601
+ std::size_t second_len = first_len * second_len_multiplier;
602
+ std::string bench_name = name + "<|A|=" + std::to_string(first_len) +
603
+ ",|B|=" + std::to_string(second_len) +
604
+ ",|A∩B|=" + std::to_string(intersection_size) + ">";
605
+ if (second_len > 8192) continue;
606
+ bm::RegisterBenchmark(bench_name.c_str(), measure_sparse<pair_t, metric_at *>, distance_func,
607
+ baseline_func, first_len, second_len, intersection_size)
608
+ ->MinTime(default_seconds)
609
+ ->Threads(default_threads);
610
+ }
611
+ }
612
+ }
613
+ }
614
+
615
+ template <simsimd_datatype_t datatype_ak, typename metric_at = void>
616
+ void curved_(std::string name, metric_at *distance_func, metric_at *baseline_func) {
617
+
618
+ using pair_t = vectors_pair_gt<datatype_ak>;
619
+ std::string bench_name = name + "<" + std::to_string(curved_dimensions) + "d>";
620
+ bm::RegisterBenchmark(bench_name.c_str(), measure_curved<pair_t, metric_at *>, distance_func, baseline_func,
621
+ curved_dimensions)
622
+ ->MinTime(default_seconds)
623
+ ->Threads(default_threads);
624
+ }
625
+
626
+ #if SIMSIMD_BUILD_BENCHMARKS_WITH_CBLAS
627
+
628
+ void dot_f32_blas(simsimd_f32_t const *a, simsimd_f32_t const *b, simsimd_size_t n, simsimd_distance_t *result) {
629
+ *result = cblas_sdot((int)n, a, 1, b, 1);
630
+ }
631
+
632
+ void dot_f64_blas(simsimd_f64_t const *a, simsimd_f64_t const *b, simsimd_size_t n, simsimd_distance_t *result) {
633
+ *result = cblas_ddot((int)n, a, 1, b, 1);
634
+ }
635
+
636
+ void dot_f32c_blas(simsimd_f32_t const *a, simsimd_f32_t const *b, simsimd_size_t n, simsimd_distance_t *result) {
637
+ simsimd_f32_t f32_result[2] = {0, 0};
638
+ cblas_cdotu_sub((int)n / 2, a, 1, b, 1, f32_result);
639
+ result[0] = f32_result[0];
640
+ result[1] = f32_result[1];
641
+ }
642
+
643
+ void dot_f64c_blas(simsimd_f64_t const *a, simsimd_f64_t const *b, simsimd_size_t n, simsimd_distance_t *result) {
644
+ cblas_zdotu_sub((int)n / 2, a, 1, b, 1, result);
645
+ }
646
+
647
+ void vdot_f32c_blas(simsimd_f32_t const *a, simsimd_f32_t const *b, simsimd_size_t n, simsimd_distance_t *result) {
648
+ simsimd_f32_t f32_result[2] = {0, 0};
649
+ cblas_cdotc_sub((int)n / 2, a, 1, b, 1, f32_result);
650
+ result[0] = f32_result[0];
651
+ result[1] = f32_result[1];
652
+ }
653
+
654
+ void vdot_f64c_blas(simsimd_f64_t const *a, simsimd_f64_t const *b, simsimd_size_t n, simsimd_distance_t *result) {
655
+ cblas_zdotc_sub((int)n / 2, a, 1, b, 1, result);
656
+ }
657
+
658
+ #endif
659
+
660
+ int main(int argc, char **argv) {
661
+ simsimd_capability_t runtime_caps = simsimd_capabilities();
662
+
663
+ // Log supported functionality
664
+ char const *flags[2] = {"false", "true"};
665
+ std::printf("Benchmarking Similarity Measures\n");
666
+ std::printf("- Compiler used native F16: %s\n", flags[SIMSIMD_NATIVE_F16]);
667
+ std::printf("- Compiler used native BF16: %s\n", flags[SIMSIMD_NATIVE_BF16]);
668
+ std::printf("- Benchmark against CBLAS: %s\n", flags[SIMSIMD_BUILD_BENCHMARKS_WITH_CBLAS]);
669
+ std::printf("\n");
670
+ std::printf("Compile-time settings:\n");
671
+ std::printf("- Arm NEON support enabled: %s\n", flags[SIMSIMD_TARGET_NEON]);
672
+ std::printf("- Arm SVE support enabled: %s\n", flags[SIMSIMD_TARGET_SVE]);
673
+ std::printf("- Arm SVE2 support enabled: %s\n", flags[SIMSIMD_TARGET_SVE2]);
674
+ std::printf("- x86 Haswell support enabled: %s\n", flags[SIMSIMD_TARGET_HASWELL]);
675
+ std::printf("- x86 Skylake support enabled: %s\n", flags[SIMSIMD_TARGET_SKYLAKE]);
676
+ std::printf("- x86 Ice Lake support enabled: %s\n", flags[SIMSIMD_TARGET_ICE]);
677
+ std::printf("- x86 Genoa support enabled: %s\n", flags[SIMSIMD_TARGET_GENOA]);
678
+ std::printf("- x86 Sapphire Rapids support enabled: %s\n", flags[SIMSIMD_TARGET_SAPPHIRE]);
679
+ std::printf("- x86 Turin support enabled: %s\n", flags[SIMSIMD_TARGET_TURIN]);
680
+ std::printf("\n");
681
+ std::printf("Run-time settings:\n");
682
+ std::printf("- Arm NEON support enabled: %s\n", flags[(runtime_caps & simsimd_cap_neon_k) != 0]);
683
+ std::printf("- Arm NEON F16 support enabled: %s\n", flags[(runtime_caps & simsimd_cap_neon_f16_k) != 0]);
684
+ std::printf("- Arm NEON BF16 support enabled: %s\n", flags[(runtime_caps & simsimd_cap_neon_bf16_k) != 0]);
685
+ std::printf("- Arm NEON I8 support enabled: %s\n", flags[(runtime_caps & simsimd_cap_neon_i8_k) != 0]);
686
+ std::printf("- Arm SVE support enabled: %s\n", flags[(runtime_caps & simsimd_cap_sve_k) != 0]);
687
+ std::printf("- Arm SVE F16 support enabled: %s\n", flags[(runtime_caps & simsimd_cap_sve_f16_k) != 0]);
688
+ std::printf("- Arm SVE BF16 support enabled: %s\n", flags[(runtime_caps & simsimd_cap_sve_bf16_k) != 0]);
689
+ std::printf("- Arm SVE I8 support enabled: %s\n", flags[(runtime_caps & simsimd_cap_sve_i8_k) != 0]);
690
+ std::printf("- Arm SVE2 support enabled: %s\n", flags[(runtime_caps & simsimd_cap_sve2_k) != 0]);
691
+ std::printf("- x86 Haswell support enabled: %s\n", flags[(runtime_caps & simsimd_cap_haswell_k) != 0]);
692
+ std::printf("- x86 Skylake support enabled: %s\n", flags[(runtime_caps & simsimd_cap_skylake_k) != 0]);
693
+ std::printf("- x86 Ice Lake support enabled: %s\n", flags[(runtime_caps & simsimd_cap_ice_k) != 0]);
694
+ std::printf("- x86 Genoa support enabled: %s\n", flags[(runtime_caps & simsimd_cap_genoa_k) != 0]);
695
+ std::printf("- x86 Sapphire Rapids support enabled: %s\n", flags[(runtime_caps & simsimd_cap_sapphire_k) != 0]);
696
+ std::printf("- x86 Turin support enabled: %s\n", flags[(runtime_caps & simsimd_cap_turin_k) != 0]);
697
+ std::printf("- x86 Sierra Forest support enabled: %s\n", flags[(runtime_caps & simsimd_cap_sierra_k) != 0]);
698
+ std::printf("\n");
699
+
700
+ // Run the benchmarks
701
+ bm::Initialize(&argc, argv);
702
+ if (bm::ReportUnrecognizedArguments(argc, argv)) return 1;
703
+
704
+ constexpr simsimd_datatype_t b8_k = simsimd_datatype_b8_k;
705
+ constexpr simsimd_datatype_t i4x2_k = simsimd_datatype_i4x2_k;
706
+ constexpr simsimd_datatype_t i8_k = simsimd_datatype_i8_k;
707
+ constexpr simsimd_datatype_t i16_k = simsimd_datatype_i16_k;
708
+ constexpr simsimd_datatype_t i32_k = simsimd_datatype_i32_k;
709
+ constexpr simsimd_datatype_t i64_k = simsimd_datatype_i64_k;
710
+ constexpr simsimd_datatype_t u8_k = simsimd_datatype_u8_k;
711
+ constexpr simsimd_datatype_t u16_k = simsimd_datatype_u16_k;
712
+ constexpr simsimd_datatype_t u32_k = simsimd_datatype_u32_k;
713
+ constexpr simsimd_datatype_t u64_k = simsimd_datatype_u64_k;
714
+ constexpr simsimd_datatype_t f64_k = simsimd_datatype_f64_k;
715
+ constexpr simsimd_datatype_t f32_k = simsimd_datatype_f32_k;
716
+ constexpr simsimd_datatype_t f16_k = simsimd_datatype_f16_k;
717
+ constexpr simsimd_datatype_t bf16_k = simsimd_datatype_bf16_k;
718
+ constexpr simsimd_datatype_t f64c_k = simsimd_datatype_f64c_k;
719
+ constexpr simsimd_datatype_t f32c_k = simsimd_datatype_f32c_k;
720
+ constexpr simsimd_datatype_t f16c_k = simsimd_datatype_f16c_k;
721
+ constexpr simsimd_datatype_t bf16c_k = simsimd_datatype_bf16c_k;
722
+
723
+ #if SIMSIMD_BUILD_BENCHMARKS_WITH_CBLAS
724
+
725
+ dense_<f32_k>("dot_f32_blas", dot_f32_blas, simsimd_dot_f32_accurate);
726
+ dense_<f64_k>("dot_f64_blas", dot_f64_blas, simsimd_dot_f64_serial);
727
+ dense_<f32c_k>("dot_f32c_blas", dot_f32c_blas, simsimd_dot_f32c_accurate);
728
+ dense_<f64c_k>("dot_f64c_blas", dot_f64c_blas, simsimd_dot_f64c_serial);
729
+ dense_<f32c_k>("vdot_f32c_blas", vdot_f32c_blas, simsimd_vdot_f32c_accurate);
730
+ dense_<f64c_k>("vdot_f64c_blas", vdot_f64c_blas, simsimd_vdot_f64c_serial);
731
+
732
+ #endif
733
+
734
+ #if SIMSIMD_TARGET_NEON
735
+ dense_<f32_k>("dot_f32_neon", simsimd_dot_f32_neon, simsimd_dot_f32_accurate);
736
+ dense_<f32_k>("cos_f32_neon", simsimd_cos_f32_neon, simsimd_cos_f32_accurate);
737
+ dense_<f32_k>("l2sq_f32_neon", simsimd_l2sq_f32_neon, simsimd_l2sq_f32_accurate);
738
+ dense_<f32_k>("l2_f32_neon", simsimd_l2_f32_neon, simsimd_l2_f32_accurate);
739
+ dense_<f32_k>("kl_f32_neon", simsimd_kl_f32_neon, simsimd_kl_f32_accurate);
740
+ dense_<f32_k>("js_f32_neon", simsimd_js_f32_neon, simsimd_js_f32_accurate);
741
+
742
+ dense_<f64_k>("cos_f64_neon", simsimd_cos_f64_neon, simsimd_cos_f64_serial);
743
+ dense_<f64_k>("l2sq_f64_neon", simsimd_l2sq_f64_neon, simsimd_l2sq_f64_serial);
744
+ dense_<f64_k>("l2_f64_neon", simsimd_l2_f64_neon, simsimd_l2_f64_serial);
745
+
746
+ dense_<i8_k>("cos_i8_neon", simsimd_cos_i8_neon, simsimd_cos_i8_serial);
747
+ dense_<i8_k>("l2sq_i8_neon", simsimd_l2sq_i8_neon, simsimd_l2sq_i8_serial);
748
+ dense_<i8_k>("l2_i8_neon", simsimd_l2_i8_neon, simsimd_l2_i8_serial);
749
+ dense_<i8_k>("dot_i8_neon", simsimd_dot_i8_neon, simsimd_dot_i8_serial);
750
+
751
+ dense_<u8_k>("cos_u8_neon", simsimd_cos_u8_neon, simsimd_cos_u8_serial);
752
+ dense_<u8_k>("l2sq_u8_neon", simsimd_l2sq_u8_neon, simsimd_l2sq_u8_serial);
753
+ dense_<u8_k>("l2_u8_neon", simsimd_l2_u8_neon, simsimd_l2_u8_serial);
754
+ dense_<u8_k>("dot_u8_neon", simsimd_dot_u8_neon, simsimd_dot_u8_serial);
755
+
756
+ dense_<b8_k>("hamming_b8_neon", simsimd_hamming_b8_neon, simsimd_hamming_b8_serial);
757
+ dense_<b8_k>("jaccard_b8_neon", simsimd_jaccard_b8_neon, simsimd_jaccard_b8_serial);
758
+
759
+ dense_<f32c_k>("dot_f32c_neon", simsimd_dot_f32c_neon, simsimd_dot_f32c_accurate);
760
+ dense_<f32c_k>("vdot_f32c_neon", simsimd_vdot_f32c_neon, simsimd_vdot_f32c_accurate);
761
+
762
+ curved_<f32_k>("bilinear_f32_neon", simsimd_bilinear_f32_neon, simsimd_bilinear_f32_accurate);
763
+ curved_<f32_k>("mahalanobis_f32_neon", simsimd_mahalanobis_f32_neon, simsimd_mahalanobis_f32_accurate);
764
+
765
+ sparse_<u16_k>("intersect_u16_neon", simsimd_intersect_u16_neon, simsimd_intersect_u16_accurate);
766
+ sparse_<u32_k>("intersect_u32_neon", simsimd_intersect_u32_neon, simsimd_intersect_u32_accurate);
767
+
768
+ fma_<f32_k>("fma_f32_neon", simsimd_fma_f32_neon, simsimd_fma_f32_accurate, simsimd_l2_f32_accurate);
769
+ fma_<f32_k>("wsum_f32_neon", simsimd_wsum_f32_neon, simsimd_wsum_f32_accurate, simsimd_l2_f32_accurate);
770
+ fma_<f32_k>("fma_f32_serial", simsimd_fma_f32_serial, simsimd_fma_f32_accurate, simsimd_l2_f32_accurate);
771
+ fma_<f32_k>("wsum_f32_serial", simsimd_wsum_f32_serial, simsimd_wsum_f32_accurate, simsimd_l2_f32_accurate);
772
+
773
+ #endif
774
+
775
+ #if SIMSIMD_TARGET_NEON_F16
776
+ dense_<f16c_k>("dot_f16c_neon", simsimd_dot_f16c_neon, simsimd_dot_f16c_accurate);
777
+ dense_<f16c_k>("vdot_f16c_neon", simsimd_vdot_f16c_neon, simsimd_vdot_f16c_accurate);
778
+
779
+ dense_<f16_k>("dot_f16_neon", simsimd_dot_f16_neon, simsimd_dot_f16_accurate);
780
+ dense_<f16_k>("cos_f16_neon", simsimd_cos_f16_neon, simsimd_cos_f16_accurate);
781
+ dense_<f16_k>("l2sq_f16_neon", simsimd_l2sq_f16_neon, simsimd_l2sq_f16_accurate);
782
+ dense_<f16_k>("l2_f16_neon", simsimd_l2_f16_neon, simsimd_l2sq_f16_accurate);
783
+ dense_<f16_k>("kl_f16_neon", simsimd_kl_f16_neon, simsimd_kl_f16_accurate);
784
+ dense_<f16_k>("js_f16_neon", simsimd_js_f16_neon, simsimd_js_f16_accurate);
785
+
786
+ curved_<f16_k>("bilinear_f16_neon", simsimd_bilinear_f16_neon, simsimd_bilinear_f16_accurate);
787
+ curved_<f16_k>("mahalanobis_f16_neon", simsimd_mahalanobis_f16_neon, simsimd_mahalanobis_f16_accurate);
788
+
789
+ fma_<f16_k>("fma_f16_neon", simsimd_fma_f16_neon, simsimd_fma_f16_accurate, simsimd_l2_f16_accurate);
790
+ fma_<f16_k>("wsum_f16_neon", simsimd_wsum_f16_neon, simsimd_wsum_f16_accurate, simsimd_l2_f16_accurate);
791
+
792
+ // FMA kernels for `u8` on NEON use `f16` arithmetic
793
+ fma_<u8_k>("fma_u8_neon", simsimd_fma_u8_neon, simsimd_fma_u8_accurate, simsimd_l2_u8_serial);
794
+ fma_<u8_k>("wsum_u8_neon", simsimd_wsum_u8_neon, simsimd_wsum_u8_accurate, simsimd_l2_u8_serial);
795
+ fma_<i8_k>("fma_i8_neon", simsimd_fma_i8_neon, simsimd_fma_i8_accurate, simsimd_l2_i8_serial);
796
+ fma_<i8_k>("wsum_i8_neon", simsimd_wsum_i8_neon, simsimd_wsum_i8_accurate, simsimd_l2_i8_serial);
797
+ #endif
798
+
799
+ #if SIMSIMD_TARGET_NEON_BF16
800
+ dense_<bf16c_k>("dot_bf16c_neon", simsimd_dot_bf16c_neon, simsimd_dot_bf16c_accurate);
801
+ dense_<bf16c_k>("vdot_bf16c_neon", simsimd_vdot_bf16c_neon, simsimd_vdot_bf16c_accurate);
802
+
803
+ dense_<bf16_k>("dot_bf16_neon", simsimd_dot_bf16_neon, simsimd_dot_bf16_accurate);
804
+ dense_<bf16_k>("cos_bf16_neon", simsimd_cos_bf16_neon, simsimd_cos_bf16_accurate);
805
+ dense_<bf16_k>("l2sq_bf16_neon", simsimd_l2sq_bf16_neon, simsimd_l2sq_bf16_accurate);
806
+ dense_<bf16_k>("l2_bf16_neon", simsimd_l2_bf16_neon, simsimd_l2_bf16_accurate);
807
+
808
+ curved_<bf16_k>("bilinear_bf16_neon", simsimd_bilinear_bf16_neon, simsimd_bilinear_bf16_accurate);
809
+ curved_<bf16_k>("mahalanobis_bf16_neon", simsimd_mahalanobis_bf16_neon, simsimd_mahalanobis_bf16_accurate);
810
+
811
+ fma_<bf16_k>("fma_bf16_neon", simsimd_fma_bf16_neon, simsimd_fma_bf16_accurate, simsimd_l2_bf16_accurate);
812
+ fma_<bf16_k>("wsum_bf16_neon", simsimd_wsum_bf16_neon, simsimd_wsum_bf16_accurate, simsimd_l2_bf16_accurate);
813
+ #endif
814
+
815
+ #if SIMSIMD_TARGET_SVE
816
+ dense_<f16_k>("dot_f16_sve", simsimd_dot_f16_sve, simsimd_dot_f16_accurate);
817
+ dense_<f16_k>("cos_f16_sve", simsimd_cos_f16_sve, simsimd_cos_f16_accurate);
818
+ dense_<f16_k>("l2sq_f16_sve", simsimd_l2sq_f16_sve, simsimd_l2sq_f16_accurate);
819
+ dense_<f16_k>("l2_f16_sve", simsimd_l2_f16_sve, simsimd_l2_f16_accurate);
820
+
821
+ dense_<bf16_k>("cos_bf16_sve", simsimd_cos_bf16_sve, simsimd_cos_bf16_accurate);
822
+ dense_<bf16_k>("l2sq_bf16_sve", simsimd_l2sq_bf16_sve, simsimd_l2sq_bf16_accurate);
823
+ dense_<bf16_k>("l2_bf16_sve", simsimd_l2_bf16_sve, simsimd_l2_bf16_accurate);
824
+
825
+ dense_<f32_k>("dot_f32_sve", simsimd_dot_f32_sve, simsimd_dot_f32_accurate);
826
+ dense_<f32_k>("cos_f32_sve", simsimd_cos_f32_sve, simsimd_cos_f32_accurate);
827
+ dense_<f32_k>("l2sq_f32_sve", simsimd_l2sq_f32_sve, simsimd_l2sq_f32_accurate);
828
+ dense_<f32_k>("l2_f32_sve", simsimd_l2_f32_sve, simsimd_l2_f32_accurate);
829
+
830
+ dense_<f64_k>("dot_f64_sve", simsimd_dot_f64_sve, simsimd_dot_f64_serial);
831
+ dense_<f64_k>("cos_f64_sve", simsimd_cos_f64_sve, simsimd_cos_f64_serial);
832
+ dense_<f64_k>("l2sq_f64_sve", simsimd_l2sq_f64_sve, simsimd_l2sq_f64_serial);
833
+ dense_<f64_k>("l2_f64_sve", simsimd_l2_f64_sve, simsimd_l2_f64_serial);
834
+
835
+ dense_<b8_k>("hamming_b8_sve", simsimd_hamming_b8_sve, simsimd_hamming_b8_serial);
836
+ dense_<b8_k>("jaccard_b8_sve", simsimd_jaccard_b8_sve, simsimd_jaccard_b8_serial);
837
+
838
+ dense_<f32c_k>("dot_f32c_sve", simsimd_dot_f32c_sve, simsimd_dot_f32c_accurate);
839
+ dense_<f32c_k>("vdot_f32c_sve", simsimd_vdot_f32c_sve, simsimd_vdot_f32c_accurate);
840
+ dense_<f64c_k>("dot_f64c_sve", simsimd_dot_f64c_sve, simsimd_dot_f64c_serial);
841
+ dense_<f64c_k>("vdot_f64c_sve", simsimd_vdot_f64c_sve, simsimd_vdot_f64c_serial);
842
+ #endif
843
+
844
+ #if SIMSIMD_TARGET_SVE_F16
845
+ dense_<f16_k>("dot_f16_sve", simsimd_dot_f16_sve, simsimd_dot_f16_accurate);
846
+ dense_<f16_k>("cos_f16_sve", simsimd_cos_f16_sve, simsimd_cos_f16_accurate);
847
+ dense_<f16_k>("l2sq_f16_sve", simsimd_l2sq_f16_sve, simsimd_l2sq_f16_accurate);
848
+ dense_<f16_k>("l2_f16_sve", simsimd_l2_f16_sve, simsimd_l2sq_f16_accurate);
849
+ dense_<f16c_k>("dot_f16c_sve", simsimd_dot_f16c_sve, simsimd_dot_f16c_accurate);
850
+ dense_<f16c_k>("vdot_f16c_sve", simsimd_vdot_f16c_sve, simsimd_vdot_f16c_accurate);
851
+ #endif
852
+
853
+ #if SIMSIMD_TARGET_SVE_BF16
854
+ dense_<bf16_k>("cos_bf16_sve", simsimd_cos_bf16_sve, simsimd_cos_bf16_accurate);
855
+ dense_<bf16_k>("l2sq_bf16_sve", simsimd_l2sq_bf16_sve, simsimd_l2sq_bf16_accurate);
856
+ dense_<bf16_k>("l2_bf16_sve", simsimd_l2_bf16_sve, simsimd_l2_bf16_accurate);
857
+ #endif
858
+
859
+ #if SIMSIMD_TARGET_SVE2
860
+ sparse_<u16_k>("intersect_u16_sve2", simsimd_intersect_u16_sve2, simsimd_intersect_u16_accurate);
861
+ sparse_<u32_k>("intersect_u32_sve2", simsimd_intersect_u32_sve2, simsimd_intersect_u32_accurate);
862
+ #endif
863
+
864
+ #if SIMSIMD_TARGET_HASWELL
865
+ dense_<f16_k>("dot_f16_haswell", simsimd_dot_f16_haswell, simsimd_dot_f16_accurate);
866
+ dense_<f16_k>("cos_f16_haswell", simsimd_cos_f16_haswell, simsimd_cos_f16_accurate);
867
+ dense_<f16_k>("l2sq_f16_haswell", simsimd_l2sq_f16_haswell, simsimd_l2sq_f16_accurate);
868
+ dense_<f16_k>("l2_f16_haswell", simsimd_l2_f16_haswell, simsimd_l2_f16_accurate);
869
+ dense_<f16_k>("kl_f16_haswell", simsimd_kl_f16_haswell, simsimd_kl_f16_accurate);
870
+ dense_<f16_k>("js_f16_haswell", simsimd_js_f16_haswell, simsimd_js_f16_accurate);
871
+
872
+ dense_<bf16_k>("dot_bf16_haswell", simsimd_dot_bf16_haswell, simsimd_dot_bf16_accurate);
873
+ dense_<bf16_k>("cos_bf16_haswell", simsimd_cos_bf16_haswell, simsimd_cos_bf16_accurate);
874
+ dense_<bf16_k>("l2sq_bf16_haswell", simsimd_l2sq_bf16_haswell, simsimd_l2sq_bf16_accurate);
875
+ dense_<bf16_k>("l2_bf16_haswell", simsimd_l2_bf16_haswell, simsimd_l2_bf16_accurate);
876
+
877
+ dense_<i8_k>("cos_i8_haswell", simsimd_cos_i8_haswell, simsimd_cos_i8_serial);
878
+ dense_<i8_k>("l2sq_i8_haswell", simsimd_l2sq_i8_haswell, simsimd_l2sq_i8_serial);
879
+ dense_<i8_k>("l2_i8_haswell", simsimd_l2_i8_haswell, simsimd_l2_i8_serial);
880
+ dense_<i8_k>("dot_i8_haswell", simsimd_dot_i8_haswell, simsimd_dot_i8_serial);
881
+
882
+ dense_<u8_k>("cos_u8_haswell", simsimd_cos_u8_haswell, simsimd_cos_u8_serial);
883
+ dense_<u8_k>("l2sq_u8_haswell", simsimd_l2sq_u8_haswell, simsimd_l2sq_u8_serial);
884
+ dense_<u8_k>("l2_u8_haswell", simsimd_l2_u8_haswell, simsimd_l2_u8_serial);
885
+ dense_<u8_k>("dot_u8_haswell", simsimd_dot_u8_haswell, simsimd_dot_u8_serial);
886
+
887
+ dense_<b8_k>("hamming_b8_haswell", simsimd_hamming_b8_haswell, simsimd_hamming_b8_serial);
888
+ dense_<b8_k>("jaccard_b8_haswell", simsimd_jaccard_b8_haswell, simsimd_jaccard_b8_serial);
889
+
890
+ dense_<f16c_k>("dot_f16c_haswell", simsimd_dot_f16c_haswell, simsimd_dot_f16c_accurate);
891
+ dense_<f16c_k>("vdot_f16c_haswell", simsimd_vdot_f16c_haswell, simsimd_vdot_f16c_accurate);
892
+ dense_<f32c_k>("dot_f32c_haswell", simsimd_dot_f32c_haswell, simsimd_dot_f32c_accurate);
893
+ dense_<f32c_k>("vdot_f32c_haswell", simsimd_vdot_f32c_haswell, simsimd_vdot_f32c_accurate);
894
+
895
+ curved_<f16_k>("bilinear_f16_haswell", simsimd_bilinear_f16_haswell, simsimd_bilinear_f16_accurate);
896
+ curved_<f16_k>("mahalanobis_f16_haswell", simsimd_mahalanobis_f16_haswell, simsimd_mahalanobis_f16_accurate);
897
+ curved_<bf16_k>("bilinear_bf16_haswell", simsimd_bilinear_bf16_haswell, simsimd_bilinear_bf16_accurate);
898
+ curved_<bf16_k>("mahalanobis_bf16_haswell", simsimd_mahalanobis_bf16_haswell, simsimd_mahalanobis_bf16_accurate);
899
+
900
+ fma_<f64_k>("fma_f64_haswell", simsimd_fma_f64_haswell, simsimd_fma_f64_serial, simsimd_l2_f64_serial);
901
+ fma_<f64_k>("wsum_f64_haswell", simsimd_wsum_f64_haswell, simsimd_wsum_f64_serial, simsimd_l2_f64_serial);
902
+ fma_<f32_k>("fma_f32_haswell", simsimd_fma_f32_haswell, simsimd_fma_f32_accurate, simsimd_l2_f32_accurate);
903
+ fma_<f32_k>("wsum_f32_haswell", simsimd_wsum_f32_haswell, simsimd_wsum_f32_accurate, simsimd_l2_f32_accurate);
904
+ fma_<f16_k>("fma_f16_haswell", simsimd_fma_f16_haswell, simsimd_fma_f16_accurate, simsimd_l2_f16_accurate);
905
+ fma_<f16_k>("wsum_f16_haswell", simsimd_wsum_f16_haswell, simsimd_wsum_f16_accurate, simsimd_l2_f16_accurate);
906
+ fma_<bf16_k>("fma_bf16_haswell", simsimd_fma_bf16_haswell, simsimd_fma_bf16_accurate, simsimd_l2_bf16_accurate);
907
+ fma_<bf16_k>("wsum_bf16_haswell", simsimd_wsum_bf16_haswell, simsimd_wsum_bf16_accurate, simsimd_l2_bf16_accurate);
908
+ fma_<i8_k>("fma_i8_haswell", simsimd_fma_i8_haswell, simsimd_fma_i8_accurate, simsimd_l2_i8_serial);
909
+ fma_<i8_k>("wsum_i8_haswell", simsimd_wsum_i8_haswell, simsimd_wsum_i8_accurate, simsimd_l2_i8_serial);
910
+ fma_<u8_k>("fma_u8_haswell", simsimd_fma_u8_haswell, simsimd_fma_u8_accurate, simsimd_l2_u8_serial);
911
+ fma_<u8_k>("wsum_u8_haswell", simsimd_wsum_u8_haswell, simsimd_wsum_u8_accurate, simsimd_l2_u8_serial);
912
+
913
+ #endif
914
+
915
+ #if SIMSIMD_TARGET_GENOA
916
+ dense_<bf16_k>("dot_bf16_genoa", simsimd_dot_bf16_genoa, simsimd_dot_bf16_accurate);
917
+ dense_<bf16_k>("cos_bf16_genoa", simsimd_cos_bf16_genoa, simsimd_cos_bf16_accurate);
918
+ dense_<bf16_k>("l2sq_bf16_genoa", simsimd_l2sq_bf16_genoa, simsimd_l2sq_bf16_accurate);
919
+ dense_<bf16_k>("l2_bf16_genoa", simsimd_l2_bf16_genoa, simsimd_l2_bf16_accurate);
920
+
921
+ dense_<bf16_k>("dot_bf16c_genoa", simsimd_dot_bf16c_genoa, simsimd_dot_bf16c_accurate);
922
+ dense_<bf16_k>("vdot_bf16c_genoa", simsimd_vdot_bf16c_genoa, simsimd_vdot_bf16c_accurate);
923
+
924
+ curved_<bf16_k>("bilinear_bf16_genoa", simsimd_bilinear_bf16_genoa, simsimd_bilinear_bf16_accurate);
925
+ curved_<bf16_k>("mahalanobis_bf16_genoa", simsimd_mahalanobis_bf16_genoa, simsimd_mahalanobis_bf16_accurate);
926
+ #endif
927
+
928
+ #if SIMSIMD_TARGET_SAPPHIRE
929
+ dense_<f16_k>("dot_f16_sapphire", simsimd_dot_f16_sapphire, simsimd_dot_f16_accurate);
930
+ dense_<f16_k>("cos_f16_sapphire", simsimd_cos_f16_sapphire, simsimd_cos_f16_accurate);
931
+ dense_<f16_k>("l2sq_f16_sapphire", simsimd_l2sq_f16_sapphire, simsimd_l2sq_f16_accurate);
932
+ dense_<f16_k>("l2_f16_sapphire", simsimd_l2_f16_sapphire, simsimd_l2_f16_accurate);
933
+ dense_<f16_k>("kl_f16_sapphire", simsimd_kl_f16_sapphire, simsimd_kl_f16_accurate);
934
+ dense_<f16_k>("js_f16_sapphire", simsimd_js_f16_sapphire, simsimd_js_f16_accurate);
935
+
936
+ dense_<f16c_k>("dot_f16c_sapphire", simsimd_dot_f16c_sapphire, simsimd_dot_f16c_accurate);
937
+ dense_<f16c_k>("vdot_f16c_sapphire", simsimd_vdot_f16c_sapphire, simsimd_vdot_f16c_accurate);
938
+
939
+ fma_<u8_k>("fma_u8_sapphire", simsimd_fma_u8_sapphire, simsimd_fma_u8_accurate, simsimd_l2_u8_serial);
940
+ fma_<u8_k>("wsum_u8_sapphire", simsimd_wsum_u8_sapphire, simsimd_wsum_u8_accurate, simsimd_l2_u8_serial);
941
+ fma_<i8_k>("fma_i8_sapphire", simsimd_fma_i8_sapphire, simsimd_fma_i8_accurate, simsimd_l2_i8_serial);
942
+ fma_<i8_k>("wsum_i8_sapphire", simsimd_wsum_i8_sapphire, simsimd_wsum_i8_accurate, simsimd_l2_i8_serial);
943
+ #endif
944
+
945
+ #if SIMSIMD_TARGET_ICE
946
+ dense_<i8_k>("cos_i8_ice", simsimd_cos_i8_ice, simsimd_cos_i8_serial);
947
+ dense_<i8_k>("l2sq_i8_ice", simsimd_l2sq_i8_ice, simsimd_l2sq_i8_serial);
948
+ dense_<i8_k>("l2_i8_ice", simsimd_l2_i8_ice, simsimd_l2_i8_serial);
949
+ dense_<i8_k>("dot_i8_ice", simsimd_dot_i8_ice, simsimd_dot_i8_serial);
950
+
951
+ dense_<u8_k>("cos_u8_ice", simsimd_cos_u8_ice, simsimd_cos_u8_serial);
952
+ dense_<u8_k>("l2sq_u8_ice", simsimd_l2sq_u8_ice, simsimd_l2sq_u8_serial);
953
+ dense_<u8_k>("l2_u8_ice", simsimd_l2_u8_ice, simsimd_l2_u8_serial);
954
+ dense_<u8_k>("dot_u8_ice", simsimd_dot_u8_ice, simsimd_dot_u8_serial);
955
+
956
+ dense_<f64_k>("dot_f64_skylake", simsimd_dot_f64_skylake, simsimd_dot_f64_serial);
957
+ dense_<f64_k>("cos_f64_skylake", simsimd_cos_f64_skylake, simsimd_cos_f64_serial);
958
+ dense_<f64_k>("l2sq_f64_skylake", simsimd_l2sq_f64_skylake, simsimd_l2sq_f64_serial);
959
+ dense_<f64_k>("l2_f64_skylake", simsimd_l2_f64_skylake, simsimd_l2_f64_serial);
960
+
961
+ dense_<b8_k>("hamming_b8_ice", simsimd_hamming_b8_ice, simsimd_hamming_b8_serial);
962
+ dense_<b8_k>("jaccard_b8_ice", simsimd_jaccard_b8_ice, simsimd_jaccard_b8_serial);
963
+
964
+ sparse_<u16_k>("intersect_u16_ice", simsimd_intersect_u16_ice, simsimd_intersect_u16_accurate);
965
+ sparse_<u32_k>("intersect_u32_ice", simsimd_intersect_u32_ice, simsimd_intersect_u32_accurate);
966
+ #endif
967
+
968
+ #if SIMSIMD_TARGET_TURIN
969
+ sparse_<u16_k>("intersect_u16_turin", simsimd_intersect_u16_turin, simsimd_intersect_u16_accurate);
970
+ sparse_<u32_k>("intersect_u32_turin", simsimd_intersect_u32_turin, simsimd_intersect_u32_accurate);
971
+ #endif
972
+
973
+ #if SIMSIMD_TARGET_SKYLAKE
974
+ dense_<f32_k>("dot_f32_skylake", simsimd_dot_f32_skylake, simsimd_dot_f32_accurate);
975
+ dense_<f32_k>("cos_f32_skylake", simsimd_cos_f32_skylake, simsimd_cos_f32_accurate);
976
+ dense_<f32_k>("l2sq_f32_skylake", simsimd_l2sq_f32_skylake, simsimd_l2sq_f32_accurate);
977
+ dense_<f32_k>("l2_f32_skylake", simsimd_l2_f32_skylake, simsimd_l2_f32_accurate);
978
+ dense_<f32_k>("kl_f32_skylake", simsimd_kl_f32_skylake, simsimd_kl_f32_accurate);
979
+ dense_<f32_k>("js_f32_skylake", simsimd_js_f32_skylake, simsimd_js_f32_accurate);
980
+
981
+ dense_<f32c_k>("dot_f32c_skylake", simsimd_dot_f32c_skylake, simsimd_dot_f32c_accurate);
982
+ dense_<f32c_k>("vdot_f32c_skylake", simsimd_vdot_f32c_skylake, simsimd_vdot_f32c_accurate);
983
+ dense_<f64c_k>("dot_f64c_skylake", simsimd_dot_f64c_skylake, simsimd_dot_f64c_serial);
984
+ dense_<f64c_k>("vdot_f64c_skylake", simsimd_vdot_f64c_skylake, simsimd_vdot_f64c_serial);
985
+
986
+ fma_<f64_k>("fma_f64_skylake", simsimd_fma_f64_skylake, simsimd_fma_f64_serial, simsimd_l2_f64_serial);
987
+ fma_<f64_k>("wsum_f64_skylake", simsimd_wsum_f64_skylake, simsimd_wsum_f64_serial, simsimd_l2_f64_serial);
988
+ fma_<f32_k>("fma_f32_skylake", simsimd_fma_f32_skylake, simsimd_fma_f32_accurate, simsimd_l2_f32_accurate);
989
+ fma_<f32_k>("wsum_f32_skylake", simsimd_wsum_f32_skylake, simsimd_wsum_f32_accurate, simsimd_l2_f32_accurate);
990
+ fma_<bf16_k>("fma_bf16_skylake", simsimd_fma_bf16_skylake, simsimd_fma_bf16_accurate, simsimd_l2_bf16_accurate);
991
+ fma_<bf16_k>("wsum_bf16_skylake", simsimd_wsum_bf16_skylake, simsimd_wsum_bf16_accurate, simsimd_l2_bf16_accurate);
992
+
993
+ #endif
994
+
995
+ sparse_<u16_k>("intersect_u16_serial", simsimd_intersect_u16_serial, simsimd_intersect_u16_accurate);
996
+ sparse_<u16_k>("intersect_u16_accurate", simsimd_intersect_u16_accurate, simsimd_intersect_u16_accurate);
997
+ sparse_<u32_k>("intersect_u32_serial", simsimd_intersect_u32_serial, simsimd_intersect_u32_accurate);
998
+ sparse_<u32_k>("intersect_u32_accurate", simsimd_intersect_u32_accurate, simsimd_intersect_u32_accurate);
999
+
1000
+ curved_<f64_k>("bilinear_f64_serial", simsimd_bilinear_f64_serial, simsimd_bilinear_f64_serial);
1001
+ curved_<f64_k>("mahalanobis_f64_serial", simsimd_mahalanobis_f64_serial, simsimd_mahalanobis_f64_serial);
1002
+ curved_<f32_k>("bilinear_f32_serial", simsimd_bilinear_f32_serial, simsimd_bilinear_f32_accurate);
1003
+ curved_<f32_k>("mahalanobis_f32_serial", simsimd_mahalanobis_f32_serial, simsimd_mahalanobis_f32_accurate);
1004
+ curved_<f16_k>("bilinear_f16_serial", simsimd_bilinear_f16_serial, simsimd_bilinear_f16_accurate);
1005
+ curved_<f16_k>("mahalanobis_f16_serial", simsimd_mahalanobis_f16_serial, simsimd_mahalanobis_f16_accurate);
1006
+ curved_<bf16_k>("bilinear_bf16_serial", simsimd_bilinear_bf16_serial, simsimd_bilinear_bf16_accurate);
1007
+ curved_<bf16_k>("mahalanobis_bf16_serial", simsimd_mahalanobis_bf16_serial, simsimd_mahalanobis_bf16_accurate);
1008
+
1009
+ dense_<bf16_k>("dot_bf16_serial", simsimd_dot_bf16_serial, simsimd_dot_bf16_accurate);
1010
+ dense_<bf16_k>("cos_bf16_serial", simsimd_cos_bf16_serial, simsimd_cos_bf16_accurate);
1011
+ dense_<bf16_k>("l2sq_bf16_serial", simsimd_l2sq_bf16_serial, simsimd_l2sq_bf16_accurate);
1012
+ dense_<bf16_k>("l2_bf16_serial", simsimd_l2_bf16_serial, simsimd_l2_bf16_accurate);
1013
+ dense_<bf16_k>("kl_bf16_serial", simsimd_kl_bf16_serial, simsimd_kl_bf16_accurate);
1014
+ dense_<bf16_k>("js_bf16_serial", simsimd_js_bf16_serial, simsimd_js_bf16_accurate);
1015
+
1016
+ dense_<f16_k>("dot_f16_serial", simsimd_dot_f16_serial, simsimd_dot_f16_accurate);
1017
+ dense_<f16_k>("cos_f16_serial", simsimd_cos_f16_serial, simsimd_cos_f16_accurate);
1018
+ dense_<f16_k>("l2sq_f16_serial", simsimd_l2sq_f16_serial, simsimd_l2sq_f16_accurate);
1019
+ dense_<f16_k>("l2_f16_serial", simsimd_l2_f16_serial, simsimd_l2_f16_accurate);
1020
+ dense_<f16_k>("kl_f16_serial", simsimd_kl_f16_serial, simsimd_kl_f16_accurate);
1021
+ dense_<f16_k>("js_f16_serial", simsimd_js_f16_serial, simsimd_js_f16_accurate);
1022
+
1023
+ dense_<f32_k>("dot_f32_serial", simsimd_dot_f32_serial, simsimd_dot_f32_accurate);
1024
+ dense_<f32_k>("cos_f32_serial", simsimd_cos_f32_serial, simsimd_cos_f32_accurate);
1025
+ dense_<f32_k>("l2sq_f32_serial", simsimd_l2sq_f32_serial, simsimd_l2sq_f32_accurate);
1026
+ dense_<f32_k>("l2_f32_serial", simsimd_l2_f32_serial, simsimd_l2_f32_accurate);
1027
+ dense_<f32_k>("kl_f32_serial", simsimd_kl_f32_serial, simsimd_kl_f32_accurate);
1028
+ dense_<f32_k>("js_f32_serial", simsimd_js_f32_serial, simsimd_js_f32_accurate);
1029
+
1030
+ dense_<f64_k>("dot_f64_serial", simsimd_dot_f64_serial, simsimd_dot_f64_serial);
1031
+ dense_<f64_k>("cos_f64_serial", simsimd_cos_f64_serial, simsimd_cos_f64_serial);
1032
+ dense_<f64_k>("l2sq_f64_serial", simsimd_l2sq_f64_serial, simsimd_l2sq_f64_serial);
1033
+ dense_<f64_k>("l2_f64_serial", simsimd_l2_f64_serial, simsimd_l2_f64_serial);
1034
+
1035
+ dense_<i8_k>("cos_i8_serial", simsimd_cos_i8_serial, simsimd_cos_i8_serial);
1036
+ dense_<i8_k>("l2sq_i8_serial", simsimd_l2sq_i8_serial, simsimd_l2sq_i8_serial);
1037
+ dense_<i8_k>("l2_i8_serial", simsimd_l2_i8_serial, simsimd_l2_i8_serial);
1038
+ dense_<i8_k>("dot_i8_serial", simsimd_dot_i8_serial, simsimd_dot_i8_serial);
1039
+
1040
+ dense_<u8_k>("cos_u8_serial", simsimd_cos_u8_serial, simsimd_cos_u8_serial);
1041
+ dense_<u8_k>("l2sq_u8_serial", simsimd_l2sq_u8_serial, simsimd_l2sq_u8_serial);
1042
+ dense_<u8_k>("l2_u8_serial", simsimd_l2_u8_serial, simsimd_l2_u8_serial);
1043
+ dense_<u8_k>("dot_u8_serial", simsimd_dot_u8_serial, simsimd_dot_u8_serial);
1044
+
1045
+ dense_<f64c_k>("dot_f64c_serial", simsimd_dot_f64c_serial, simsimd_dot_f64c_serial);
1046
+ dense_<f32c_k>("dot_f32c_serial", simsimd_dot_f32c_serial, simsimd_dot_f32c_accurate);
1047
+ dense_<f16c_k>("dot_f16c_serial", simsimd_dot_f16c_serial, simsimd_dot_f16c_accurate);
1048
+ dense_<bf16c_k>("dot_bf16c_serial", simsimd_dot_bf16c_serial, simsimd_dot_bf16c_accurate);
1049
+ dense_<f64c_k>("vdot_f64c_serial", simsimd_vdot_f64c_serial, simsimd_vdot_f64c_serial);
1050
+ dense_<f32c_k>("vdot_f32c_serial", simsimd_vdot_f32c_serial, simsimd_vdot_f32c_accurate);
1051
+ dense_<f16c_k>("vdot_f16c_serial", simsimd_vdot_f16c_serial, simsimd_vdot_f16c_accurate);
1052
+ dense_<bf16c_k>("vdot_bf16c_serial", simsimd_vdot_bf16c_serial, simsimd_vdot_bf16c_accurate);
1053
+
1054
+ dense_<f16c_k>("vdot_f16c_serial", simsimd_vdot_f16c_serial, simsimd_vdot_f16c_accurate);
1055
+ dense_<bf16c_k>("vdot_bf16c_serial", simsimd_vdot_bf16c_serial, simsimd_vdot_bf16c_accurate);
1056
+
1057
+ dense_<b8_k>("hamming_b8_serial", simsimd_hamming_b8_serial, simsimd_hamming_b8_serial);
1058
+ dense_<b8_k>("jaccard_b8_serial", simsimd_jaccard_b8_serial, simsimd_jaccard_b8_serial);
1059
+
1060
+ fma_<f16_k>("fma_f16_serial", simsimd_fma_f16_serial, simsimd_fma_f16_accurate, simsimd_l2_f16_accurate);
1061
+ fma_<f16_k>("wsum_f16_serial", simsimd_wsum_f16_serial, simsimd_wsum_f16_accurate, simsimd_l2_f16_accurate);
1062
+ fma_<u8_k>("fma_u8_serial", simsimd_fma_u8_serial, simsimd_fma_u8_accurate, simsimd_l2_u8_serial);
1063
+ fma_<u8_k>("wsum_u8_serial", simsimd_wsum_u8_serial, simsimd_wsum_u8_accurate, simsimd_l2_u8_serial);
1064
+ fma_<i8_k>("fma_i8_serial", simsimd_fma_i8_serial, simsimd_fma_i8_accurate, simsimd_l2_i8_serial);
1065
+ fma_<i8_k>("wsum_i8_serial", simsimd_wsum_i8_serial, simsimd_wsum_i8_accurate, simsimd_l2_i8_serial);
1066
+
1067
+ bm::RunSpecifiedBenchmarks();
1068
+ bm::Shutdown();
1069
+ return 0;
1070
+ }