usearch 2.11.1 → 2.13.1

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 (50) hide show
  1. package/README.md +100 -30
  2. package/binding.gyp +11 -12
  3. package/c/README.md +3 -0
  4. package/c/lib.cpp +50 -13
  5. package/c/test.c +60 -55
  6. package/c/usearch.h +16 -0
  7. package/cpp/CMakeLists.txt +6 -0
  8. package/cpp/README.md +34 -13
  9. package/cpp/bench.cpp +99 -47
  10. package/cpp/test.cpp +947 -132
  11. package/include/usearch/index.hpp +894 -272
  12. package/include/usearch/index_dense.hpp +418 -156
  13. package/include/usearch/index_plugins.hpp +99 -49
  14. package/javascript/dist/cjs/usearch.d.ts +39 -1
  15. package/javascript/dist/cjs/usearch.js +11 -1
  16. package/javascript/dist/esm/usearch.d.ts +39 -1
  17. package/javascript/dist/esm/usearch.js +11 -1
  18. package/javascript/tsconfig-esm.json +3 -2
  19. package/javascript/usearch.ts +18 -7
  20. package/package.json +2 -2
  21. package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
  22. package/prebuilds/linux-arm64/usearch.node +0 -0
  23. package/prebuilds/linux-x64/usearch.node +0 -0
  24. package/simsimd/.cmake-format.py +19 -0
  25. package/simsimd/CONTRIBUTING.md +49 -8
  26. package/simsimd/Cargo.lock +1 -1
  27. package/simsimd/Cargo.toml +10 -5
  28. package/simsimd/Package.swift +16 -0
  29. package/simsimd/README.md +69 -15
  30. package/simsimd/binding.gyp +14 -19
  31. package/simsimd/build.rs +2 -0
  32. package/simsimd/c/lib.c +18 -0
  33. package/simsimd/include/module.modulemap +4 -0
  34. package/simsimd/include/simsimd/binary.h +2 -0
  35. package/simsimd/include/simsimd/dot.h +466 -32
  36. package/simsimd/include/simsimd/probability.h +20 -10
  37. package/simsimd/include/simsimd/simsimd.h +444 -98
  38. package/simsimd/include/simsimd/spatial.h +480 -44
  39. package/simsimd/include/simsimd/types.h +161 -39
  40. package/simsimd/javascript/simsimd.ts +26 -2
  41. package/simsimd/package.json +1 -1
  42. package/simsimd/rust/benches/cosine.rs +8 -2
  43. package/simsimd/rust/benches/native.rs +240 -2
  44. package/simsimd/rust/benches/sqeuclidean.rs +8 -2
  45. package/simsimd/rust/lib.rs +50 -13
  46. package/simsimd/swift/source/SimSIMD.swift +123 -0
  47. package/simsimd/swift/tests/SimSIMDTests.swift +92 -0
  48. package/prebuilds/win32-ia32/usearch.node +0 -0
  49. package/prebuilds/win32-x64/usearch.node +0 -0
  50. package/simsimd/.releaserc +0 -87
@@ -11,12 +11,21 @@
11
11
  #ifndef SIMSIMD_TYPES_H
12
12
  #define SIMSIMD_TYPES_H
13
13
 
14
- /* Annotation for the public API symbols:
15
- *
16
- * - `SIMSIMD_PUBLIC` is used for functions that are part of the public API.
17
- * - `SIMSIMD_INTERNAL` is used for internal helper functions with unstable APIs.
18
- * - `SIMSIMD_DYNAMIC` is used for functions that are part of the public API, but are dispatched at runtime.
19
- */
14
+ // Inferring target OS: Windows, MacOS, or Linux
15
+ #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
16
+ #define SIMSIMD_DEFINED_WINDOWS
17
+ #elif defined(__APPLE__) && defined(__MACH__)
18
+ #define SIMSIMD_DEFINED_APPLE
19
+ #elif defined(__linux__)
20
+ #define SIMSIMD_DEFINED_LINUX
21
+ #endif
22
+
23
+ // Annotation for the public API symbols:
24
+ //
25
+ // - `SIMSIMD_PUBLIC` is used for functions that are part of the public API.
26
+ // - `SIMSIMD_INTERNAL` is used for internal helper functions with unstable APIs.
27
+ // - `SIMSIMD_DYNAMIC` is used for functions that are part of the public API, but are dispatched at runtime.
28
+ //
20
29
  #if defined(_WIN32) || defined(__CYGWIN__)
21
30
  #define SIMSIMD_DYNAMIC __declspec(dllexport)
22
31
  #define SIMSIMD_PUBLIC inline static
@@ -59,6 +68,16 @@
59
68
  #endif // defined(__ARM_NEON)
60
69
  #endif // !defined(SIMSIMD_TARGET_NEON)
61
70
 
71
+ #if !defined(SIMSIMD_TARGET_NEON_I8)
72
+ #define SIMSIMD_TARGET_NEON_I8 SIMSIMD_TARGET_NEON
73
+ #endif // !defined(SIMSIMD_TARGET_NEON_I8)
74
+ #if !defined(SIMSIMD_TARGET_NEON_F16)
75
+ #define SIMSIMD_TARGET_NEON_F16 SIMSIMD_TARGET_NEON
76
+ #endif // !defined(SIMSIMD_TARGET_NEON_F16)
77
+ #if !defined(SIMSIMD_TARGET_NEON_BF16)
78
+ #define SIMSIMD_TARGET_NEON_BF16 SIMSIMD_TARGET_NEON
79
+ #endif // !defined(SIMSIMD_TARGET_NEON_BF16)
80
+
62
81
  // Compiling for Arm: SIMSIMD_TARGET_SVE
63
82
  #if !defined(SIMSIMD_TARGET_SVE) || (SIMSIMD_TARGET_SVE && !SIMSIMD_TARGET_ARM)
64
83
  #if defined(__ARM_FEATURE_SVE)
@@ -69,6 +88,16 @@
69
88
  #endif // defined(__ARM_FEATURE_SVE)
70
89
  #endif // !defined(SIMSIMD_TARGET_SVE)
71
90
 
91
+ #if !defined(SIMSIMD_TARGET_SVE_I8)
92
+ #define SIMSIMD_TARGET_SVE_I8 SIMSIMD_TARGET_SVE
93
+ #endif // !defined(SIMSIMD_TARGET_SVE_I8)
94
+ #if !defined(SIMSIMD_TARGET_SVE_F16)
95
+ #define SIMSIMD_TARGET_SVE_F16 SIMSIMD_TARGET_SVE
96
+ #endif // !defined(SIMSIMD_TARGET_SVE_F16)
97
+ #if !defined(SIMSIMD_TARGET_SVE_BF16)
98
+ #define SIMSIMD_TARGET_SVE_BF16 SIMSIMD_TARGET_SVE
99
+ #endif // !defined(SIMSIMD_TARGET_SVE_BF16)
100
+
72
101
  // Compiling for x86: SIMSIMD_TARGET_HASWELL
73
102
  //
74
103
  // Starting with Ivy Bridge, Intel supports the `F16C` extensions for fast half-precision
@@ -87,28 +116,6 @@
87
116
 
88
117
  // Compiling for x86: SIMSIMD_TARGET_SKYLAKE, SIMSIMD_TARGET_ICE, SIMSIMD_TARGET_SAPPHIRE
89
118
  //
90
- // It's important to provide fine-grained controls over AVX512 families, as they are very fragmented:
91
- // - Intel Skylake servers: F, CD, VL, DQ, BW
92
- // - Intel Cascade Lake workstations: F, CD, VL, DQ, BW, VNNI
93
- // > In other words, it extends Skylake with VNNI support
94
- // - Intel Sunny Cove (Ice Lake) servers:
95
- // F, CD, VL, DQ, BW, VNNI, VPOPCNTDQ, IFMA, VBMI, VAES, GFNI, VBMI2, BITALG, VPCLMULQDQ
96
- // - AMD Zen4 (Genoa):
97
- // F, CD, VL, DQ, BW, VNNI, VPOPCNTDQ, IFMA, VBMI, VAES, GFNI, VBMI2, BITALG, VPCLMULQDQ, BF16
98
- // > In other words, it extends Sunny Cove with BF16 support
99
- // - Golden Cove (Sapphire Rapids): extends Zen4 and Sunny Cove with FP16 support
100
- //
101
- // Intel Palm Cove was an irrelevant intermediate release extending Skylake with IFMA and VBMI.
102
- // Intel Willow Cove was an irrelevant intermediate release extending Sunny Cove with VP2INTERSECT,
103
- // that aren't supported by any other CPU built to date... and those are only available in Tiger Lake laptops.
104
- // Intel Cooper Lake was the only intermediary platform, that supported BF16, but not FP16.
105
- // It's mostly used in 4-socket and 8-socket high-memory configurations.
106
- //
107
- // In practical terms, it makes sense to differentiate only 3 AVX512 generations:
108
- // 1. Skylake (pre 2019): supports single-precision dot-products.
109
- // 2. Ice Lake (2019-2021): advanced integer algorithms.
110
- // 3. Sapphire Rapids (2023+): advanced mixed-precision float processing.
111
- //
112
119
  // To list all available macros for x86, take a recent compiler, like GCC 12 and run:
113
120
  // gcc-12 -march=sapphirerapids -dM -E - < /dev/null | egrep "SSE|AVX" | sort
114
121
  // On Arm machines you may want to check for other flags:
@@ -131,6 +138,14 @@
131
138
  #define SIMSIMD_TARGET_ICE 0
132
139
  #endif
133
140
  #endif // !defined(SIMSIMD_TARGET_ICE)
141
+ #if !defined(SIMSIMD_TARGET_GENOA) || (SIMSIMD_TARGET_GENOA && !SIMSIMD_TARGET_X86)
142
+ #if defined(__AVX512BF16__)
143
+ #define SIMSIMD_TARGET_GENOA 1
144
+ #else
145
+ #undef SIMSIMD_TARGET_GENOA
146
+ #define SIMSIMD_TARGET_GENOA 0
147
+ #endif
148
+ #endif // !defined(SIMSIMD_TARGET_GENOA)
134
149
  #if !defined(SIMSIMD_TARGET_SAPPHIRE) || (SIMSIMD_TARGET_SAPPHIRE && !SIMSIMD_TARGET_X86)
135
150
  #if defined(__AVX512FP16__)
136
151
  #define SIMSIMD_TARGET_SAPPHIRE 1
@@ -185,19 +200,19 @@ typedef float simsimd_f32_t;
185
200
  typedef double simsimd_f64_t;
186
201
  typedef signed char simsimd_i8_t;
187
202
  typedef unsigned char simsimd_b8_t;
203
+ typedef long long simsimd_i64_t;
188
204
  typedef unsigned long long simsimd_u64_t;
189
205
 
190
206
  typedef simsimd_u64_t simsimd_size_t;
191
207
  typedef simsimd_f64_t simsimd_distance_t;
192
208
 
193
- #if !defined(SIMSIMD_NATIVE_F16) || SIMSIMD_NATIVE_F16
194
- /**
195
- * @brief Half-precision floating-point type.
209
+ /* @brief Half-precision floating-point type.
196
210
  *
197
- * - GCC or Clang on 64-bit ARM: `__fp16`, may require `-mfp16-format` option.
211
+ * - GCC or Clang on 64-bit Arm: `__fp16`, may require `-mfp16-format` option.
198
212
  * - GCC or Clang on 64-bit x86: `_Float16`.
199
213
  * - Default: `unsigned short`.
200
214
  */
215
+ #if !defined(SIMSIMD_NATIVE_F16) || SIMSIMD_NATIVE_F16
201
216
  #if (defined(__GNUC__) || defined(__clang__)) && (defined(__ARM_ARCH) || defined(__aarch64__)) && \
202
217
  (defined(__ARM_FP16_FORMAT_IEEE))
203
218
  #if !defined(SIMSIMD_NATIVE_F16)
@@ -219,17 +234,55 @@ typedef _Float16 simsimd_f16_t;
219
234
  typedef unsigned short simsimd_f16_t;
220
235
  #endif
221
236
 
237
+ #if !defined(SIMSIMD_NATIVE_BF16) || SIMSIMD_NATIVE_BF16
238
+ /**
239
+ * @brief Half-precision brain-float type.
240
+ *
241
+ * - GCC or Clang on 64-bit Arm: `__bf16`
242
+ * - GCC or Clang on 64-bit x86: `_BFloat16`.
243
+ * - Default: `unsigned short`.
244
+ *
245
+ * @warning Apple Clang has hard time with bf16.
246
+ * https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms
247
+ * https://forums.developer.apple.com/forums/thread/726201
248
+ */
249
+ #if (defined(__GNUC__) || defined(__clang__)) && (defined(__ARM_ARCH) || defined(__aarch64__)) && \
250
+ (defined(__ARM_BF16_FORMAT_ALTERNATIVE))
251
+ #if !defined(SIMSIMD_NATIVE_BF16)
252
+ #define SIMSIMD_NATIVE_BF16 1
253
+ #endif
254
+ typedef __fp16 simsimd_bf16_t;
255
+ #elif ((defined(__GNUC__) || defined(__clang__)) && (defined(__x86_64__) || defined(__i386__)) && \
256
+ (defined(__SSE2__) || defined(__AVX512F__)))
257
+ typedef _Float16 simsimd_bf16_t;
258
+ #if !defined(SIMSIMD_NATIVE_BF16)
259
+ #define SIMSIMD_NATIVE_BF16 1
260
+ #endif
261
+ #else // Unknown compiler or architecture
262
+ #define SIMSIMD_NATIVE_BF16 0
263
+ #endif // Unknown compiler or architecture
264
+ #endif // !SIMSIMD_NATIVE_BF16
265
+
266
+ #if !SIMSIMD_NATIVE_BF16
267
+ typedef unsigned short simsimd_bf16_t;
268
+ #endif
269
+
222
270
  /**
223
271
  * @brief Alias for the half-precision floating-point type on Arm.
224
- * Clang and GCC bring the `float16_t` symbol when you compile for Aarch64.
225
- * MSVC lacks it, and it's `vld1_f16`-like intrinsics are in reality macros,
226
- * that cast to 16-bit integers internally, instead of using floats.
272
+ *
273
+ * Clang and GCC bring the `float16_t` symbol when you compile for Aarch64.
274
+ * MSVC lacks it, and it's `vld1_f16`-like intrinsics are in reality macros,
275
+ * that cast to 16-bit integers internally, instead of using floats.
276
+ * Some of those are defined as aliases, so we use `#define` preprocessor
277
+ * directives instead of `typedef` to avoid errors.
227
278
  */
228
279
  #if SIMSIMD_TARGET_ARM
229
280
  #if defined(_MSC_VER)
230
- typedef simsimd_f16_t simsimd_f16_for_arm_simd_t;
281
+ #define simsimd_f16_for_arm_simd_t simsimd_f16_t
282
+ #define simsimd_bf16_for_arm_simd_t simsimd_bf16_t
231
283
  #else
232
- typedef float16_t simsimd_f16_for_arm_simd_t;
284
+ #define simsimd_f16_for_arm_simd_t float16_t
285
+ #define simsimd_bf16_for_arm_simd_t bfloat16_t
233
286
  #endif
234
287
  #endif
235
288
 
@@ -241,9 +294,21 @@ typedef float16_t simsimd_f16_for_arm_simd_t;
241
294
  */
242
295
  #ifndef SIMSIMD_UNCOMPRESS_F16
243
296
  #if SIMSIMD_NATIVE_F16
244
- #define SIMSIMD_UNCOMPRESS_F16(x) SIMSIMD_IDENTIFY(x)
297
+ #define SIMSIMD_UNCOMPRESS_F16(x) (SIMSIMD_IDENTIFY(x))
245
298
  #else
246
- #define SIMSIMD_UNCOMPRESS_F16(x) simsimd_uncompress_f16(x)
299
+ #define SIMSIMD_UNCOMPRESS_F16(x) (simsimd_uncompress_f16(x))
300
+ #endif
301
+ #endif
302
+
303
+ /**
304
+ * @brief Returns the value of the half-precision brain floating-point number,
305
+ * potentially decompressed into single-precision.
306
+ */
307
+ #ifndef SIMSIMD_UNCOMPRESS_BF16
308
+ #if SIMSIMD_NATIVE_BF16
309
+ #define SIMSIMD_UNCOMPRESS_BF16(x) (SIMSIMD_IDENTIFY(x))
310
+ #else
311
+ #define SIMSIMD_UNCOMPRESS_BF16(x) (simsimd_uncompress_bf16(x))
247
312
  #endif
248
313
  #endif
249
314
 
@@ -302,6 +367,63 @@ SIMSIMD_PUBLIC simsimd_f32_t simsimd_uncompress_f16(unsigned short x) {
302
367
  return result_union.f;
303
368
  }
304
369
 
370
+ /**
371
+ * @brief Compresses a `float` to an `f16` representation (IEEE-754 16-bit floating-point format).
372
+ *
373
+ * @warning This function won't handle boundary conditions well.
374
+ *
375
+ * https://stackoverflow.com/a/60047308
376
+ * https://gist.github.com/milhidaka/95863906fe828198f47991c813dbe233
377
+ * https://github.com/OpenCyphal/libcanard/blob/636795f4bc395f56af8d2c61d3757b5e762bb9e5/canard.c#L811-L834
378
+ */
379
+ SIMSIMD_PUBLIC unsigned short simsimd_compress_f16(simsimd_f32_t x) {
380
+ union float_or_unsigned_int_t {
381
+ float f;
382
+ unsigned int i;
383
+ } x_union;
384
+ x_union.f = x;
385
+
386
+ unsigned int b = x_union.i + 0x00001000;
387
+ unsigned int e = (b & 0x7F800000) >> 23;
388
+ unsigned int m = b & 0x007FFFFF;
389
+ unsigned short result = ((b & 0x80000000) >> 16) | (e > 112) * ((((e - 112) << 10) & 0x7C00) | (m >> 13)) |
390
+ ((e < 113) & (e > 101)) * ((((0x007FF000 + m) >> (125 - e)) + 1) >> 1) |
391
+ ((e > 143) * 0x7FFF);
392
+ return result;
393
+ }
394
+
395
+ /**
396
+ * @brief For compilers that don't natively support the `__bf16` type,
397
+ * upcasts contents into a more conventional `float`.
398
+ *
399
+ * https://stackoverflow.com/questions/55253233/convert-fp32-to-bfloat16-in-c/55254307#55254307
400
+ * https://cloud.google.com/blog/products/ai-machine-learning/bfloat16-the-secret-to-high-performance-on-cloud-tpus
401
+ */
402
+ SIMSIMD_PUBLIC simsimd_f32_t simsimd_uncompress_bf16(unsigned short x) {
403
+ union float_or_unsigned_int_t {
404
+ float f;
405
+ unsigned int i;
406
+ };
407
+ union float_or_unsigned_int_t result_union;
408
+ result_union.i = x << 16; // Zero extends the mantissa
409
+ return result_union.f;
410
+ }
411
+
412
+ /**
413
+ * @brief Compresses a `float` to a `bf16` representation.
414
+ */
415
+ SIMSIMD_PUBLIC unsigned short simsimd_compress_bf16(simsimd_f32_t x) {
416
+ union float_or_unsigned_int_t {
417
+ float f;
418
+ unsigned int i;
419
+ };
420
+ union float_or_unsigned_int_t value;
421
+ value.f = x;
422
+ value.i >>= 16;
423
+ value.i &= 0xFFFF;
424
+ return (unsigned short)value.i;
425
+ }
426
+
305
427
  #ifdef __cplusplus
306
428
  } // extern "C"
307
429
  #endif
@@ -102,6 +102,27 @@ export const jensenshannon = (a: Float64Array | Float32Array, b: Float64Array |
102
102
  return compiled.jensenshannon(a, b);
103
103
  };
104
104
 
105
+ /**
106
+ * Quantizes a floating-point vector into a binary vector (1 for positive values, 0 for non-positive values) and packs the result into a Uint8Array, where each element represents 8 binary values from the original vector.
107
+ * This function is useful for preparing data for bitwise distance or similarity computations, such as Hamming or Jaccard indices.
108
+ *
109
+ * @param {Float32Array | Float64Array | Int8Array} vector The floating-point vector to be quantized and packed.
110
+ * @returns {Uint8Array} A Uint8Array where each byte represents 8 binary quantized values from the input vector.
111
+ */
112
+ export const toBinary = (vector: Float32Array | Float64Array | Int8Array): Uint8Array => {
113
+ const byteLength = Math.ceil(vector.length / 8);
114
+ const packedVector = new Uint8Array(byteLength);
115
+
116
+ for (let i = 0; i < vector.length; i++) {
117
+ if (vector[i] > 0) {
118
+ const byteIndex = Math.floor(i / 8);
119
+ const bitPosition = 7 - (i % 8);
120
+ packedVector[byteIndex] |= (1 << bitPosition);
121
+ }
122
+ }
123
+
124
+ return packedVector;
125
+ };
105
126
  export default {
106
127
  dot,
107
128
  inner,
@@ -111,10 +132,13 @@ export default {
111
132
  jaccard,
112
133
  kullbackleibler,
113
134
  jensenshannon,
135
+ toBinary,
114
136
  };
115
137
 
116
- // utility functions to help find native builds
117
-
138
+ /**
139
+ * @brief Finds the directory where the native build of the simsimd module is located.
140
+ * @param {string} dir - The directory to start the search from.
141
+ */
118
142
  function getBuildDir(dir: string) {
119
143
  if (existsSync(path.join(dir, "build"))) return dir;
120
144
  if (existsSync(path.join(dir, "prebuilds"))) return dir;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simsimd",
3
- "version": "4.2.2",
3
+ "version": "5.0.0",
4
4
  "description": "Fastest SIMD-Accelerated Vector Similarity Functions for x86 and Arm",
5
5
  "homepage": "https://github.com/ashvardanian/simsimd",
6
6
  "author": "Ash Vardanian",
@@ -17,8 +17,14 @@ pub fn cos_benchmark(c: &mut Criterion) {
17
17
  group.bench_with_input(BenchmarkId::new("SimSIMD", i), &i, |b, _| {
18
18
  b.iter(|| SimSIMD::cosine(&inputs.0, &inputs.1))
19
19
  });
20
- group.bench_with_input(BenchmarkId::new("Rust Native", i), &i, |b, _| {
21
- b.iter(|| native::cos_similarity_cpu(&inputs.0, &inputs.1))
20
+ group.bench_with_input(BenchmarkId::new("Rust Procedural", i), &i, |b, _| {
21
+ b.iter(|| native::baseline_cos_procedural(&inputs.0, &inputs.1))
22
+ });
23
+ group.bench_with_input(BenchmarkId::new("Rust Functional", i), &i, |b, _| {
24
+ b.iter(|| native::baseline_cos_functional(&inputs.0, &inputs.1))
25
+ });
26
+ group.bench_with_input(BenchmarkId::new("Rust Unrolled", i), &i, |b, _| {
27
+ b.iter(|| native::baseline_cos_unrolled(&inputs.0, &inputs.1))
22
28
  });
23
29
  }
24
30
  }
@@ -5,7 +5,7 @@ pub(crate) fn generate_random_vector(dim: usize) -> Vec<f32> {
5
5
  (0..dim).map(|_| rand::thread_rng().gen()).collect()
6
6
  }
7
7
 
8
- pub(crate) fn cos_similarity_cpu(a: &[f32], b: &[f32]) -> Option<f32> {
8
+ pub(crate) fn baseline_cos_functional(a: &[f32], b: &[f32]) -> Option<f32> {
9
9
  if a.len() != b.len() {
10
10
  return None;
11
11
  }
@@ -21,10 +21,248 @@ pub(crate) fn cos_similarity_cpu(a: &[f32], b: &[f32]) -> Option<f32> {
21
21
  Some(1.0 - (dot_product / (norm_a.sqrt() * norm_b.sqrt())))
22
22
  }
23
23
 
24
- pub(crate) fn squared_euclidean_cpu(a: &[f32], b: &[f32]) -> Option<f32> {
24
+ pub(crate) fn baseline_l2sq_functional(a: &[f32], b: &[f32]) -> Option<f32> {
25
25
  if a.len() != b.len() {
26
26
  return None;
27
27
  }
28
28
 
29
29
  Some(a.iter().zip(b).map(|(x, y)| (x - y).powi(2)).sum())
30
30
  }
31
+
32
+ pub(crate) fn baseline_cos_procedural(a: &[f32], b: &[f32]) -> Option<f32> {
33
+ if a.len() != b.len() {
34
+ return None;
35
+ }
36
+
37
+ let mut dot_product = 0.0;
38
+ let mut norm_a = 0.0;
39
+ let mut norm_b = 0.0;
40
+
41
+ for i in 0..a.len() {
42
+ dot_product += a[i] * b[i];
43
+ norm_a += a[i] * a[i];
44
+ norm_b += b[i] * b[i];
45
+ }
46
+
47
+ Some(1.0 - (dot_product / (norm_a.sqrt() * norm_b.sqrt())))
48
+ }
49
+
50
+ pub(crate) fn baseline_l2sq_procedural(a: &[f32], b: &[f32]) -> Option<f32> {
51
+ if a.len() != b.len() {
52
+ return None;
53
+ }
54
+
55
+ let mut sum = 0.0;
56
+ for i in 0..a.len() {
57
+ let diff = a[i] - b[i];
58
+ sum += diff * diff;
59
+ }
60
+
61
+ Some(sum)
62
+ }
63
+
64
+ pub(crate) fn baseline_cos_unrolled(a: &[f32], b: &[f32]) -> Option<f32> {
65
+ if a.len() != b.len() {
66
+ return None;
67
+ }
68
+
69
+ let mut i = 0;
70
+ let mut remainder = a.len() % 8;
71
+
72
+ // We do this manual unrolling to allow the compiler to vectorize
73
+ // the loop and avoid some branching even if we're not doing it explicitly.
74
+ // This made a significant difference in benchmarking ~4x
75
+ let mut acc1 = 0.0;
76
+ let mut acc2 = 0.0;
77
+ let mut acc3 = 0.0;
78
+ let mut acc4 = 0.0;
79
+ let mut acc5 = 0.0;
80
+ let mut acc6 = 0.0;
81
+ let mut acc7 = 0.0;
82
+ let mut acc8 = 0.0;
83
+
84
+ let mut norm_a_acc1 = 0.0;
85
+ let mut norm_a_acc2 = 0.0;
86
+ let mut norm_a_acc3 = 0.0;
87
+ let mut norm_a_acc4 = 0.0;
88
+ let mut norm_a_acc5 = 0.0;
89
+ let mut norm_a_acc6 = 0.0;
90
+ let mut norm_a_acc7 = 0.0;
91
+ let mut norm_a_acc8 = 0.0;
92
+
93
+ let mut norm_b_acc1 = 0.0;
94
+ let mut norm_b_acc2 = 0.0;
95
+ let mut norm_b_acc3 = 0.0;
96
+ let mut norm_b_acc4 = 0.0;
97
+ let mut norm_b_acc5 = 0.0;
98
+ let mut norm_b_acc6 = 0.0;
99
+ let mut norm_b_acc7 = 0.0;
100
+ let mut norm_b_acc8 = 0.0;
101
+
102
+ while i < (a.len() - remainder) {
103
+ let a1 = unsafe { *a.get_unchecked(i) };
104
+ let a2 = unsafe { *a.get_unchecked(i + 1) };
105
+ let a3 = unsafe { *a.get_unchecked(i + 2) };
106
+ let a4 = unsafe { *a.get_unchecked(i + 3) };
107
+ let a5 = unsafe { *a.get_unchecked(i + 4) };
108
+ let a6 = unsafe { *a.get_unchecked(i + 5) };
109
+ let a7 = unsafe { *a.get_unchecked(i + 6) };
110
+ let a8 = unsafe { *a.get_unchecked(i + 7) };
111
+
112
+ let b1 = unsafe { *b.get_unchecked(i) };
113
+ let b2 = unsafe { *b.get_unchecked(i + 1) };
114
+ let b3 = unsafe { *b.get_unchecked(i + 2) };
115
+ let b4 = unsafe { *b.get_unchecked(i + 3) };
116
+ let b5 = unsafe { *b.get_unchecked(i + 4) };
117
+ let b6 = unsafe { *b.get_unchecked(i + 5) };
118
+ let b7 = unsafe { *b.get_unchecked(i + 6) };
119
+ let b8 = unsafe { *b.get_unchecked(i + 7) };
120
+
121
+ acc1 += (a1 * b1);
122
+ acc2 += (a2 * b2);
123
+ acc3 += (a3 * b3);
124
+ acc4 += (a4 * b4);
125
+ acc5 += (a5 * b5);
126
+ acc6 += (a6 * b6);
127
+ acc7 += (a7 * b7);
128
+ acc8 += (a8 * b8);
129
+
130
+ norm_a_acc1 += (a1 * a1);
131
+ norm_a_acc2 += (a2 * a2);
132
+ norm_a_acc3 += (a3 * a3);
133
+ norm_a_acc4 += (a4 * a4);
134
+ norm_a_acc5 += (a5 * a5);
135
+ norm_a_acc6 += (a6 * a6);
136
+ norm_a_acc7 += (a7 * a7);
137
+ norm_a_acc8 += (a8 * a8);
138
+
139
+ norm_b_acc1 += (b1 * b1);
140
+ norm_b_acc2 += (b2 * b2);
141
+ norm_b_acc3 += (b3 * b3);
142
+ norm_b_acc4 += (b4 * b4);
143
+ norm_b_acc5 += (b5 * b5);
144
+ norm_b_acc6 += (b6 * b6);
145
+ norm_b_acc7 += (b7 * b7);
146
+ norm_b_acc8 += (b8 * b8);
147
+
148
+ i += 8;
149
+ }
150
+
151
+ acc1 += acc2;
152
+ acc3 += acc4;
153
+ acc5 += acc6;
154
+ acc7 += acc8;
155
+
156
+ norm_a_acc1 += norm_a_acc2;
157
+ norm_a_acc3 += norm_a_acc4;
158
+ norm_a_acc5 += norm_a_acc6;
159
+ norm_a_acc7 += norm_a_acc8;
160
+
161
+ norm_b_acc1 += norm_b_acc2;
162
+ norm_b_acc3 += norm_b_acc4;
163
+ norm_b_acc5 += norm_b_acc6;
164
+ norm_b_acc7 += norm_b_acc8;
165
+
166
+ acc1 += acc3;
167
+ acc5 += acc7;
168
+
169
+ norm_a_acc1 += norm_a_acc3;
170
+ norm_a_acc5 += norm_a_acc7;
171
+
172
+ norm_b_acc1 += norm_b_acc3;
173
+ norm_b_acc5 += norm_b_acc7;
174
+
175
+ acc1 += acc5;
176
+ norm_a_acc1 += norm_a_acc5;
177
+ norm_b_acc1 += norm_b_acc5;
178
+
179
+ while i < a.len() {
180
+ let a = unsafe { *a.get_unchecked(i) };
181
+ let b = unsafe { *b.get_unchecked(i) };
182
+
183
+ acc1 += (a * b);
184
+ norm_a_acc1 += (a * a);
185
+ norm_b_acc1 += (b * b);
186
+ }
187
+
188
+ Some(1.0 - (acc1 / (norm_a_acc1 * norm_b_acc1).sqrt()))
189
+ }
190
+
191
+ pub(crate) fn baseline_l2sq_unrolled(a: &[f32], b: &[f32]) -> Option<f32> {
192
+ if a.len() != b.len() {
193
+ return None;
194
+ }
195
+ let mut i = 0;
196
+ let mut remainder = a.len() % 8;
197
+
198
+ // We do this manual unrolling to allow the compiler to vectorize
199
+ // the loop and avoid some branching even if we're not doing it explicitly.
200
+ // This made a significant difference in benchmarking ~4x
201
+ let mut acc1 = 0.0;
202
+ let mut acc2 = 0.0;
203
+ let mut acc3 = 0.0;
204
+ let mut acc4 = 0.0;
205
+ let mut acc5 = 0.0;
206
+ let mut acc6 = 0.0;
207
+ let mut acc7 = 0.0;
208
+ let mut acc8 = 0.0;
209
+
210
+ while i < (a.len() - remainder) {
211
+ let a1 = unsafe { *a.get_unchecked(i) };
212
+ let a2 = unsafe { *a.get_unchecked(i + 1) };
213
+ let a3 = unsafe { *a.get_unchecked(i + 2) };
214
+ let a4 = unsafe { *a.get_unchecked(i + 3) };
215
+ let a5 = unsafe { *a.get_unchecked(i + 4) };
216
+ let a6 = unsafe { *a.get_unchecked(i + 5) };
217
+ let a7 = unsafe { *a.get_unchecked(i + 6) };
218
+ let a8 = unsafe { *a.get_unchecked(i + 7) };
219
+
220
+ let b1 = unsafe { *b.get_unchecked(i) };
221
+ let b2 = unsafe { *b.get_unchecked(i + 1) };
222
+ let b3 = unsafe { *b.get_unchecked(i + 2) };
223
+ let b4 = unsafe { *b.get_unchecked(i + 3) };
224
+ let b5 = unsafe { *b.get_unchecked(i + 4) };
225
+ let b6 = unsafe { *b.get_unchecked(i + 5) };
226
+ let b7 = unsafe { *b.get_unchecked(i + 6) };
227
+ let b8 = unsafe { *b.get_unchecked(i + 7) };
228
+
229
+ let diff1 = a1 - b1;
230
+ let diff2 = a2 - b2;
231
+ let diff3 = a3 - b3;
232
+ let diff4 = a4 - b4;
233
+ let diff5 = a5 - b5;
234
+ let diff6 = a6 - b6;
235
+ let diff7 = a7 - b7;
236
+ let diff8 = a8 - b8;
237
+
238
+ acc1 += (diff1 * diff1);
239
+ acc2 += (diff2 * diff2);
240
+ acc3 += (diff3 * diff3);
241
+ acc4 += (diff4 * diff4);
242
+ acc5 += (diff5 * diff5);
243
+ acc6 += (diff6 * diff6);
244
+ acc7 += (diff7 * diff7);
245
+ acc8 += (diff8 * diff8);
246
+
247
+ i += 8;
248
+ }
249
+
250
+ acc1 += acc2;
251
+ acc3 += acc4;
252
+ acc5 += acc6;
253
+ acc7 += acc8;
254
+
255
+ acc1 += acc3;
256
+ acc5 += acc7;
257
+
258
+ acc1 += acc5;
259
+
260
+ while i < a.len() {
261
+ let a = unsafe { *a.get_unchecked(i) };
262
+ let b = unsafe { *b.get_unchecked(i) };
263
+ let diff = a - b;
264
+ acc1 += (diff * diff);
265
+ }
266
+
267
+ Some(acc1)
268
+ }
@@ -17,8 +17,14 @@ pub fn l2sq_benchmark(c: &mut Criterion) {
17
17
  group.bench_with_input(BenchmarkId::new("SimSIMD", i), &i, |b, _| {
18
18
  b.iter(|| SimSIMD::sqeuclidean(&inputs.0, &inputs.1))
19
19
  });
20
- group.bench_with_input(BenchmarkId::new("Rust Native", i), &i, |b, _| {
21
- b.iter(|| native::squared_euclidean_cpu(&inputs.0, &inputs.1))
20
+ group.bench_with_input(BenchmarkId::new("Rust Procedural", i), &i, |b, _| {
21
+ b.iter(|| native::baseline_l2sq_procedural(&inputs.0, &inputs.1))
22
+ });
23
+ group.bench_with_input(BenchmarkId::new("Rust Functional", i), &i, |b, _| {
24
+ b.iter(|| native::baseline_l2sq_functional(&inputs.0, &inputs.1))
25
+ });
26
+ group.bench_with_input(BenchmarkId::new("Rust Unrolled", i), &i, |b, _| {
27
+ b.iter(|| native::baseline_l2sq_unrolled(&inputs.0, &inputs.1))
22
28
  });
23
29
  }
24
30
  }