usearch 2.21.1 → 2.21.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/include/usearch/index.hpp +22 -1
- package/package.json +1 -1
- package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
- package/prebuilds/linux-arm64/usearch.node +0 -0
- package/prebuilds/linux-x64/usearch.node +0 -0
- package/simsimd/CONTRIBUTING.md +16 -9
- package/simsimd/Cargo.toml +7 -11
- package/simsimd/MANIFEST.in +1 -0
- package/simsimd/README.md +21 -20
- package/simsimd/build.rs +4 -3
- package/simsimd/c/lib.c +1 -1
- package/simsimd/include/simsimd/binary.h +11 -10
- package/simsimd/include/simsimd/dot.h +4 -4
- package/simsimd/include/simsimd/geospatial.h +1 -1
- package/simsimd/include/simsimd/mesh.h +1 -1
- package/simsimd/include/simsimd/probability.h +14 -14
- package/simsimd/include/simsimd/simsimd.h +86 -6
- package/simsimd/include/simsimd/sparse.h +60 -105
- package/simsimd/include/simsimd/types.h +4 -3
- package/simsimd/javascript/fallback.ts +10 -14
- package/simsimd/javascript/lib.c +1 -1
- package/simsimd/javascript/simsimd.ts +4 -4
- package/simsimd/package.json +1 -1
- package/simsimd/rust/lib.rs +288 -130
- package/simsimd/scripts/bench.cxx +38 -7
- package/simsimd/scripts/bench.rs +876 -0
- package/simsimd/scripts/test.py +1 -1
- package/simsimd/Cargo.lock +0 -713
- package/simsimd/scripts/bench_cosine.rs +0 -41
- package/simsimd/scripts/bench_native.rs +0 -272
- package/simsimd/scripts/bench_sqeuclidean.rs +0 -41
|
@@ -0,0 +1,876 @@
|
|
|
1
|
+
//! Unified SimSIMD Benchmark Suite
|
|
2
|
+
//!
|
|
3
|
+
//! Compares SimSIMD vs native Rust implementations using Criterion.
|
|
4
|
+
//! Reports both performance and accuracy metrics.
|
|
5
|
+
//!
|
|
6
|
+
//! Run with:
|
|
7
|
+
//! ```bash
|
|
8
|
+
//! cargo bench --bench bench -- --quiet --noplot
|
|
9
|
+
//!
|
|
10
|
+
//! # Or with custom dimensions:
|
|
11
|
+
//! SIMSIMD_BENCH_DENSE_DIMENSIONS=2048 cargo bench --bench bench -- --quiet --noplot
|
|
12
|
+
//! ```
|
|
13
|
+
use std::hint::black_box;
|
|
14
|
+
use std::mem::size_of;
|
|
15
|
+
use std::time::Duration;
|
|
16
|
+
|
|
17
|
+
use criterion::{criterion_group, criterion_main, Criterion, Throughput};
|
|
18
|
+
use rand::rngs::StdRng;
|
|
19
|
+
use rand::Rng;
|
|
20
|
+
use rand::SeedableRng;
|
|
21
|
+
|
|
22
|
+
use simsimd::SpatialSimilarity as SimSIMD;
|
|
23
|
+
|
|
24
|
+
const BOLD: &str = "\x1b[1m";
|
|
25
|
+
const RESET: &str = "\x1b[0m";
|
|
26
|
+
|
|
27
|
+
fn get_dense_dimensions() -> usize {
|
|
28
|
+
std::env::var("SIMSIMD_BENCH_DENSE_DIMENSIONS")
|
|
29
|
+
.ok()
|
|
30
|
+
.and_then(|s| s.parse().ok())
|
|
31
|
+
.unwrap_or(1536)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
#[allow(dead_code)]
|
|
35
|
+
fn get_curved_dimensions() -> usize {
|
|
36
|
+
std::env::var("SIMSIMD_BENCH_CURVED_DIMENSIONS")
|
|
37
|
+
.ok()
|
|
38
|
+
.and_then(|s| s.parse().ok())
|
|
39
|
+
.unwrap_or(8)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fn calculate_errors(baseline: &[f64], contender: &[f64]) -> (f64, f64) {
|
|
43
|
+
let mut mean_delta = 0.0f64;
|
|
44
|
+
let mut mean_relative_error = 0.0f64;
|
|
45
|
+
|
|
46
|
+
for (&b, &c) in baseline.iter().zip(contender.iter()) {
|
|
47
|
+
let abs_delta = (b - c).abs();
|
|
48
|
+
mean_delta += abs_delta;
|
|
49
|
+
if abs_delta != 0.0 && b != 0.0 {
|
|
50
|
+
mean_relative_error += abs_delta / b.abs();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
mean_delta /= baseline.len() as f64;
|
|
55
|
+
mean_relative_error /= baseline.len() as f64;
|
|
56
|
+
(mean_delta, mean_relative_error)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
fn print_error(abs_err: f64, rel_err: f64) {
|
|
60
|
+
println!(
|
|
61
|
+
" abs error: {}{:.6e}{}",
|
|
62
|
+
BOLD, abs_err, RESET
|
|
63
|
+
);
|
|
64
|
+
println!(
|
|
65
|
+
" rel error: {}{:.6e}{}",
|
|
66
|
+
BOLD, rel_err, RESET
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// region: Accurate Baseline Implementations (f64)
|
|
71
|
+
|
|
72
|
+
fn accurate_cos(a: &[f32], b: &[f32]) -> f64 {
|
|
73
|
+
let mut dot = 0.0f64;
|
|
74
|
+
let mut norm_a = 0.0f64;
|
|
75
|
+
let mut norm_b = 0.0f64;
|
|
76
|
+
for i in 0..a.len() {
|
|
77
|
+
let (av, bv) = (a[i] as f64, b[i] as f64);
|
|
78
|
+
dot += av * bv;
|
|
79
|
+
norm_a += av * av;
|
|
80
|
+
norm_b += bv * bv;
|
|
81
|
+
}
|
|
82
|
+
1.0 - dot / (norm_a * norm_b).sqrt()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
fn accurate_l2sq(a: &[f32], b: &[f32]) -> f64 {
|
|
86
|
+
let mut sum = 0.0f64;
|
|
87
|
+
for i in 0..a.len() {
|
|
88
|
+
let diff = (a[i] - b[i]) as f64;
|
|
89
|
+
sum += diff * diff;
|
|
90
|
+
}
|
|
91
|
+
sum
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
fn accurate_dot(a: &[f32], b: &[f32]) -> f64 {
|
|
95
|
+
let mut sum = 0.0f64;
|
|
96
|
+
for i in 0..a.len() {
|
|
97
|
+
sum += (a[i] as f64) * (b[i] as f64);
|
|
98
|
+
}
|
|
99
|
+
-sum
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
fn accurate_l2(a: &[f32], b: &[f32]) -> f64 {
|
|
103
|
+
accurate_l2sq(a, b).sqrt()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
fn accurate_i8_cos(a: &[i8], b: &[i8]) -> f64 {
|
|
107
|
+
let mut dot = 0i32;
|
|
108
|
+
let mut norm_a = 0i32;
|
|
109
|
+
let mut norm_b = 0i32;
|
|
110
|
+
for i in 0..a.len() {
|
|
111
|
+
let (av, bv) = (a[i] as i32, b[i] as i32);
|
|
112
|
+
dot += av * bv;
|
|
113
|
+
norm_a += av * av;
|
|
114
|
+
norm_b += bv * bv;
|
|
115
|
+
}
|
|
116
|
+
let dot_f = dot as f32;
|
|
117
|
+
let norm_a_f = norm_a as f32;
|
|
118
|
+
let norm_b_f = norm_b as f32;
|
|
119
|
+
(1.0 - dot_f / (norm_a_f * norm_b_f).sqrt()) as f64
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
fn accurate_i8_dot(a: &[i8], b: &[i8]) -> f64 {
|
|
123
|
+
let mut sum = 0i32;
|
|
124
|
+
for i in 0..a.len() {
|
|
125
|
+
sum += (a[i] as i32) * (b[i] as i32);
|
|
126
|
+
}
|
|
127
|
+
-(sum as f64)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
fn accurate_i8_l2sq(a: &[i8], b: &[i8]) -> f64 {
|
|
131
|
+
let mut sum = 0i32;
|
|
132
|
+
for i in 0..a.len() {
|
|
133
|
+
let diff = (a[i] - b[i]) as i32;
|
|
134
|
+
sum += diff * diff;
|
|
135
|
+
}
|
|
136
|
+
sum as f64
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
fn accurate_i8_l2(a: &[i8], b: &[i8]) -> f64 {
|
|
140
|
+
accurate_i8_l2sq(a, b).sqrt()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// endregion: Accurate Baseline Implementations (f64)
|
|
144
|
+
|
|
145
|
+
// region: Baseline Implementations - Cosine Similarity
|
|
146
|
+
|
|
147
|
+
fn baseline_cos_procedural(a: &[f32], b: &[f32]) -> f64 {
|
|
148
|
+
let mut dot = 0.0f32;
|
|
149
|
+
let mut norm_a = 0.0f32;
|
|
150
|
+
let mut norm_b = 0.0f32;
|
|
151
|
+
for i in 0..a.len() {
|
|
152
|
+
let (av, bv) = (a[i], b[i]);
|
|
153
|
+
dot += av * bv;
|
|
154
|
+
norm_a += av * av;
|
|
155
|
+
norm_b += bv * bv;
|
|
156
|
+
}
|
|
157
|
+
(1.0 - dot / (norm_a * norm_b).sqrt()) as f64
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
fn baseline_cos_functional(a: &[f32], b: &[f32]) -> f64 {
|
|
161
|
+
let (dot, norm_a, norm_b) = a
|
|
162
|
+
.iter()
|
|
163
|
+
.zip(b)
|
|
164
|
+
.map(|(a, b)| (*a * *b, *a * *a, *b * *b))
|
|
165
|
+
.fold((0.0f32, 0.0f32, 0.0f32), |acc, x| {
|
|
166
|
+
(acc.0 + x.0, acc.1 + x.1, acc.2 + x.2)
|
|
167
|
+
});
|
|
168
|
+
(1.0 - (dot / (norm_a.sqrt() * norm_b.sqrt()))) as f64
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
fn baseline_cos_unrolled(a: &[f32], b: &[f32]) -> f64 {
|
|
172
|
+
let mut i = 0;
|
|
173
|
+
let remainder = a.len() % 8;
|
|
174
|
+
let mut acc = [0.0f32; 8];
|
|
175
|
+
let mut norm_a = [0.0f32; 8];
|
|
176
|
+
let mut norm_b = [0.0f32; 8];
|
|
177
|
+
|
|
178
|
+
unsafe {
|
|
179
|
+
while i < a.len() - remainder {
|
|
180
|
+
let [a1, a2, a3, a4, a5, a6, a7, a8] = [
|
|
181
|
+
*a.get_unchecked(i),
|
|
182
|
+
*a.get_unchecked(i + 1),
|
|
183
|
+
*a.get_unchecked(i + 2),
|
|
184
|
+
*a.get_unchecked(i + 3),
|
|
185
|
+
*a.get_unchecked(i + 4),
|
|
186
|
+
*a.get_unchecked(i + 5),
|
|
187
|
+
*a.get_unchecked(i + 6),
|
|
188
|
+
*a.get_unchecked(i + 7),
|
|
189
|
+
];
|
|
190
|
+
let [b1, b2, b3, b4, b5, b6, b7, b8] = [
|
|
191
|
+
*b.get_unchecked(i),
|
|
192
|
+
*b.get_unchecked(i + 1),
|
|
193
|
+
*b.get_unchecked(i + 2),
|
|
194
|
+
*b.get_unchecked(i + 3),
|
|
195
|
+
*b.get_unchecked(i + 4),
|
|
196
|
+
*b.get_unchecked(i + 5),
|
|
197
|
+
*b.get_unchecked(i + 6),
|
|
198
|
+
*b.get_unchecked(i + 7),
|
|
199
|
+
];
|
|
200
|
+
|
|
201
|
+
acc[0] += a1 * b1;
|
|
202
|
+
acc[1] += a2 * b2;
|
|
203
|
+
acc[2] += a3 * b3;
|
|
204
|
+
acc[3] += a4 * b4;
|
|
205
|
+
acc[4] += a5 * b5;
|
|
206
|
+
acc[5] += a6 * b6;
|
|
207
|
+
acc[6] += a7 * b7;
|
|
208
|
+
acc[7] += a8 * b8;
|
|
209
|
+
norm_a[0] += a1 * a1;
|
|
210
|
+
norm_a[1] += a2 * a2;
|
|
211
|
+
norm_a[2] += a3 * a3;
|
|
212
|
+
norm_a[3] += a4 * a4;
|
|
213
|
+
norm_a[4] += a5 * a5;
|
|
214
|
+
norm_a[5] += a6 * a6;
|
|
215
|
+
norm_a[6] += a7 * a7;
|
|
216
|
+
norm_a[7] += a8 * a8;
|
|
217
|
+
norm_b[0] += b1 * b1;
|
|
218
|
+
norm_b[1] += b2 * b2;
|
|
219
|
+
norm_b[2] += b3 * b3;
|
|
220
|
+
norm_b[3] += b4 * b4;
|
|
221
|
+
norm_b[4] += b5 * b5;
|
|
222
|
+
norm_b[5] += b6 * b6;
|
|
223
|
+
norm_b[6] += b7 * b7;
|
|
224
|
+
norm_b[7] += b8 * b8;
|
|
225
|
+
i += 8;
|
|
226
|
+
}
|
|
227
|
+
while i < a.len() {
|
|
228
|
+
let (av, bv) = (*a.get_unchecked(i), *b.get_unchecked(i));
|
|
229
|
+
acc[0] += av * bv;
|
|
230
|
+
norm_a[0] += av * av;
|
|
231
|
+
norm_b[0] += bv * bv;
|
|
232
|
+
i += 1;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
let dot: f32 = acc.iter().sum();
|
|
237
|
+
let na: f32 = norm_a.iter().sum();
|
|
238
|
+
let nb: f32 = norm_b.iter().sum();
|
|
239
|
+
(1.0 - (dot / (na.sqrt() * nb.sqrt()))) as f64
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// endregion: Baseline Implementations - Cosine Similarity
|
|
243
|
+
|
|
244
|
+
// region: Baseline Implementations - Squared Euclidean Distance
|
|
245
|
+
|
|
246
|
+
fn baseline_l2sq_procedural(a: &[f32], b: &[f32]) -> f64 {
|
|
247
|
+
let mut sum = 0.0f32;
|
|
248
|
+
for i in 0..a.len() {
|
|
249
|
+
let diff = a[i] - b[i];
|
|
250
|
+
sum += diff * diff;
|
|
251
|
+
}
|
|
252
|
+
sum as f64
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
fn baseline_l2sq_functional(a: &[f32], b: &[f32]) -> f64 {
|
|
256
|
+
a.iter()
|
|
257
|
+
.zip(b)
|
|
258
|
+
.map(|(x, y)| {
|
|
259
|
+
let diff = *x - *y;
|
|
260
|
+
diff * diff
|
|
261
|
+
})
|
|
262
|
+
.sum::<f32>() as f64
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
fn baseline_l2sq_unrolled(a: &[f32], b: &[f32]) -> f64 {
|
|
266
|
+
let mut i = 0;
|
|
267
|
+
let remainder = a.len() % 8;
|
|
268
|
+
let mut acc = [0.0f32; 8];
|
|
269
|
+
|
|
270
|
+
unsafe {
|
|
271
|
+
while i < a.len() - remainder {
|
|
272
|
+
let [a1, a2, a3, a4, a5, a6, a7, a8] = [
|
|
273
|
+
*a.get_unchecked(i),
|
|
274
|
+
*a.get_unchecked(i + 1),
|
|
275
|
+
*a.get_unchecked(i + 2),
|
|
276
|
+
*a.get_unchecked(i + 3),
|
|
277
|
+
*a.get_unchecked(i + 4),
|
|
278
|
+
*a.get_unchecked(i + 5),
|
|
279
|
+
*a.get_unchecked(i + 6),
|
|
280
|
+
*a.get_unchecked(i + 7),
|
|
281
|
+
];
|
|
282
|
+
let [b1, b2, b3, b4, b5, b6, b7, b8] = [
|
|
283
|
+
*b.get_unchecked(i),
|
|
284
|
+
*b.get_unchecked(i + 1),
|
|
285
|
+
*b.get_unchecked(i + 2),
|
|
286
|
+
*b.get_unchecked(i + 3),
|
|
287
|
+
*b.get_unchecked(i + 4),
|
|
288
|
+
*b.get_unchecked(i + 5),
|
|
289
|
+
*b.get_unchecked(i + 6),
|
|
290
|
+
*b.get_unchecked(i + 7),
|
|
291
|
+
];
|
|
292
|
+
|
|
293
|
+
let [d1, d2, d3, d4, d5, d6, d7, d8] = [
|
|
294
|
+
a1 - b1,
|
|
295
|
+
a2 - b2,
|
|
296
|
+
a3 - b3,
|
|
297
|
+
a4 - b4,
|
|
298
|
+
a5 - b5,
|
|
299
|
+
a6 - b6,
|
|
300
|
+
a7 - b7,
|
|
301
|
+
a8 - b8,
|
|
302
|
+
];
|
|
303
|
+
acc[0] += d1 * d1;
|
|
304
|
+
acc[1] += d2 * d2;
|
|
305
|
+
acc[2] += d3 * d3;
|
|
306
|
+
acc[3] += d4 * d4;
|
|
307
|
+
acc[4] += d5 * d5;
|
|
308
|
+
acc[5] += d6 * d6;
|
|
309
|
+
acc[6] += d7 * d7;
|
|
310
|
+
acc[7] += d8 * d8;
|
|
311
|
+
i += 8;
|
|
312
|
+
}
|
|
313
|
+
while i < a.len() {
|
|
314
|
+
let diff = *a.get_unchecked(i) - *b.get_unchecked(i);
|
|
315
|
+
acc[0] += diff * diff;
|
|
316
|
+
i += 1;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
acc.iter().sum::<f32>() as f64
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// endregion: Baseline Implementations - Squared Euclidean Distance
|
|
324
|
+
|
|
325
|
+
// region: Baseline Implementations - Dot Product
|
|
326
|
+
|
|
327
|
+
fn baseline_dot_procedural(a: &[f32], b: &[f32]) -> f64 {
|
|
328
|
+
let mut sum = 0.0f32;
|
|
329
|
+
for i in 0..a.len() {
|
|
330
|
+
sum += a[i] * b[i];
|
|
331
|
+
}
|
|
332
|
+
-(sum as f64)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
fn baseline_dot_functional(a: &[f32], b: &[f32]) -> f64 {
|
|
336
|
+
-(a.iter().zip(b).map(|(x, y)| *x * *y).sum::<f32>() as f64)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
fn baseline_dot_unrolled(a: &[f32], b: &[f32]) -> f64 {
|
|
340
|
+
let mut i = 0;
|
|
341
|
+
let remainder = a.len() % 8;
|
|
342
|
+
let mut acc = [0.0f32; 8];
|
|
343
|
+
|
|
344
|
+
unsafe {
|
|
345
|
+
while i < a.len() - remainder {
|
|
346
|
+
let [a1, a2, a3, a4, a5, a6, a7, a8] = [
|
|
347
|
+
*a.get_unchecked(i),
|
|
348
|
+
*a.get_unchecked(i + 1),
|
|
349
|
+
*a.get_unchecked(i + 2),
|
|
350
|
+
*a.get_unchecked(i + 3),
|
|
351
|
+
*a.get_unchecked(i + 4),
|
|
352
|
+
*a.get_unchecked(i + 5),
|
|
353
|
+
*a.get_unchecked(i + 6),
|
|
354
|
+
*a.get_unchecked(i + 7),
|
|
355
|
+
];
|
|
356
|
+
let [b1, b2, b3, b4, b5, b6, b7, b8] = [
|
|
357
|
+
*b.get_unchecked(i),
|
|
358
|
+
*b.get_unchecked(i + 1),
|
|
359
|
+
*b.get_unchecked(i + 2),
|
|
360
|
+
*b.get_unchecked(i + 3),
|
|
361
|
+
*b.get_unchecked(i + 4),
|
|
362
|
+
*b.get_unchecked(i + 5),
|
|
363
|
+
*b.get_unchecked(i + 6),
|
|
364
|
+
*b.get_unchecked(i + 7),
|
|
365
|
+
];
|
|
366
|
+
|
|
367
|
+
acc[0] += a1 * b1;
|
|
368
|
+
acc[1] += a2 * b2;
|
|
369
|
+
acc[2] += a3 * b3;
|
|
370
|
+
acc[3] += a4 * b4;
|
|
371
|
+
acc[4] += a5 * b5;
|
|
372
|
+
acc[5] += a6 * b6;
|
|
373
|
+
acc[6] += a7 * b7;
|
|
374
|
+
acc[7] += a8 * b8;
|
|
375
|
+
i += 8;
|
|
376
|
+
}
|
|
377
|
+
while i < a.len() {
|
|
378
|
+
acc[0] += *a.get_unchecked(i) * *b.get_unchecked(i);
|
|
379
|
+
i += 1;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
-(acc.iter().sum::<f32>() as f64)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// endregion: Baseline Implementations - Dot Product
|
|
387
|
+
|
|
388
|
+
// region: Baseline Implementations - Euclidean Distance
|
|
389
|
+
|
|
390
|
+
fn baseline_l2_procedural(a: &[f32], b: &[f32]) -> f64 {
|
|
391
|
+
baseline_l2sq_procedural(a, b).sqrt()
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
fn baseline_l2_functional(a: &[f32], b: &[f32]) -> f64 {
|
|
395
|
+
baseline_l2sq_functional(a, b).sqrt()
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
fn baseline_l2_unrolled(a: &[f32], b: &[f32]) -> f64 {
|
|
399
|
+
baseline_l2sq_unrolled(a, b).sqrt()
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// endregion: Baseline Implementations - Euclidean Distance
|
|
403
|
+
|
|
404
|
+
// region: Baseline Implementations - i8 (i8 -> f64)
|
|
405
|
+
|
|
406
|
+
fn baseline_i8_cos_procedural(a: &[i8], b: &[i8]) -> f64 {
|
|
407
|
+
let mut dot = 0i32;
|
|
408
|
+
let mut norm_a = 0i32;
|
|
409
|
+
let mut norm_b = 0i32;
|
|
410
|
+
for i in 0..a.len() {
|
|
411
|
+
let (av, bv) = (a[i] as i32, b[i] as i32);
|
|
412
|
+
dot += av * bv;
|
|
413
|
+
norm_a += av * av;
|
|
414
|
+
norm_b += bv * bv;
|
|
415
|
+
}
|
|
416
|
+
let dot_f = dot as f32;
|
|
417
|
+
let norm_a_f = norm_a as f32;
|
|
418
|
+
let norm_b_f = norm_b as f32;
|
|
419
|
+
(1.0 - dot_f / (norm_a_f * norm_b_f).sqrt()) as f64
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
fn baseline_i8_dot_procedural(a: &[i8], b: &[i8]) -> f64 {
|
|
423
|
+
let mut sum = 0i32;
|
|
424
|
+
for i in 0..a.len() {
|
|
425
|
+
sum += (a[i] as i32) * (b[i] as i32);
|
|
426
|
+
}
|
|
427
|
+
-(sum as f64)
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
fn baseline_i8_l2sq_procedural(a: &[i8], b: &[i8]) -> f64 {
|
|
431
|
+
let mut sum = 0i32;
|
|
432
|
+
for i in 0..a.len() {
|
|
433
|
+
let diff = (a[i] - b[i]) as i32;
|
|
434
|
+
sum += diff * diff;
|
|
435
|
+
}
|
|
436
|
+
sum as f64
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
fn baseline_i8_l2_procedural(a: &[i8], b: &[i8]) -> f64 {
|
|
440
|
+
baseline_i8_l2sq_procedural(a, b).sqrt()
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// endregion: Baseline Implementations - i8 (i8 -> f64)
|
|
444
|
+
|
|
445
|
+
// region: Benchmark Functions
|
|
446
|
+
|
|
447
|
+
pub fn f32_cosine_benchmark(c: &mut Criterion) {
|
|
448
|
+
let dimensions = get_dense_dimensions();
|
|
449
|
+
let bench_pairs_count = (128 * 1024 * 1024) / (dimensions * size_of::<f32>() * 2);
|
|
450
|
+
let bench_pairs: Vec<_> = (0..bench_pairs_count)
|
|
451
|
+
.map(|i| {
|
|
452
|
+
let mut rng = StdRng::seed_from_u64(1000 + i as u64);
|
|
453
|
+
let a: Vec<f32> = (0..dimensions).map(|_| rng.random()).collect();
|
|
454
|
+
let b: Vec<f32> = (0..dimensions).map(|_| rng.random()).collect();
|
|
455
|
+
(a, b)
|
|
456
|
+
})
|
|
457
|
+
.collect();
|
|
458
|
+
|
|
459
|
+
let accurate: Vec<f64> = bench_pairs
|
|
460
|
+
.iter()
|
|
461
|
+
.map(|(a, b)| accurate_cos(a, b))
|
|
462
|
+
.collect();
|
|
463
|
+
let procedural: Vec<f64> = bench_pairs
|
|
464
|
+
.iter()
|
|
465
|
+
.map(|(a, b)| baseline_cos_procedural(a, b))
|
|
466
|
+
.collect();
|
|
467
|
+
let functional: Vec<f64> = bench_pairs
|
|
468
|
+
.iter()
|
|
469
|
+
.map(|(a, b)| baseline_cos_functional(a, b))
|
|
470
|
+
.collect();
|
|
471
|
+
let unrolled: Vec<f64> = bench_pairs
|
|
472
|
+
.iter()
|
|
473
|
+
.map(|(a, b)| baseline_cos_unrolled(a, b))
|
|
474
|
+
.collect();
|
|
475
|
+
let simsimd: Vec<f64> = bench_pairs
|
|
476
|
+
.iter()
|
|
477
|
+
.map(|(a, b)| SimSIMD::cosine(a, b).unwrap())
|
|
478
|
+
.collect();
|
|
479
|
+
|
|
480
|
+
let simsimd_err = calculate_errors(&accurate, &simsimd);
|
|
481
|
+
let procedural_err = calculate_errors(&accurate, &procedural);
|
|
482
|
+
let functional_err = calculate_errors(&accurate, &functional);
|
|
483
|
+
let unrolled_err = calculate_errors(&accurate, &unrolled);
|
|
484
|
+
|
|
485
|
+
let mut group = c.benchmark_group(format!("f32/{}d/cosine", dimensions));
|
|
486
|
+
let bytes_per_op = (2 * dimensions * size_of::<f32>() * bench_pairs_count) as u64;
|
|
487
|
+
group.throughput(Throughput::Bytes(bytes_per_op));
|
|
488
|
+
group
|
|
489
|
+
.sample_size(50)
|
|
490
|
+
.measurement_time(Duration::from_secs(2))
|
|
491
|
+
.noise_threshold(0.05);
|
|
492
|
+
|
|
493
|
+
group.bench_function("simsimd", |b| {
|
|
494
|
+
b.iter(|| {
|
|
495
|
+
for (a, b) in &bench_pairs {
|
|
496
|
+
black_box(SimSIMD::cosine(a, b));
|
|
497
|
+
}
|
|
498
|
+
})
|
|
499
|
+
});
|
|
500
|
+
print_error(simsimd_err.0, simsimd_err.1);
|
|
501
|
+
|
|
502
|
+
group.bench_function("procedural", |b| {
|
|
503
|
+
b.iter(|| {
|
|
504
|
+
for (a, b) in &bench_pairs {
|
|
505
|
+
black_box(baseline_cos_procedural(a, b));
|
|
506
|
+
}
|
|
507
|
+
})
|
|
508
|
+
});
|
|
509
|
+
print_error(procedural_err.0, procedural_err.1);
|
|
510
|
+
|
|
511
|
+
group.bench_function("functional", |b| {
|
|
512
|
+
b.iter(|| {
|
|
513
|
+
for (a, b) in &bench_pairs {
|
|
514
|
+
black_box(baseline_cos_functional(a, b));
|
|
515
|
+
}
|
|
516
|
+
})
|
|
517
|
+
});
|
|
518
|
+
print_error(functional_err.0, functional_err.1);
|
|
519
|
+
|
|
520
|
+
group.bench_function("unrolled", |b| {
|
|
521
|
+
b.iter(|| {
|
|
522
|
+
for (a, b) in &bench_pairs {
|
|
523
|
+
black_box(baseline_cos_unrolled(a, b));
|
|
524
|
+
}
|
|
525
|
+
})
|
|
526
|
+
});
|
|
527
|
+
print_error(unrolled_err.0, unrolled_err.1);
|
|
528
|
+
group.finish();
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
pub fn f32_dot_benchmark(c: &mut Criterion) {
|
|
532
|
+
let dimensions = get_dense_dimensions();
|
|
533
|
+
let bench_pairs_count = (128 * 1024 * 1024) / (dimensions * size_of::<f32>() * 2);
|
|
534
|
+
let bench_pairs: Vec<_> = (0..bench_pairs_count)
|
|
535
|
+
.map(|i| {
|
|
536
|
+
let mut rng = StdRng::seed_from_u64(1000 + i as u64);
|
|
537
|
+
let a: Vec<f32> = (0..dimensions).map(|_| rng.random()).collect();
|
|
538
|
+
let b: Vec<f32> = (0..dimensions).map(|_| rng.random()).collect();
|
|
539
|
+
(a, b)
|
|
540
|
+
})
|
|
541
|
+
.collect();
|
|
542
|
+
|
|
543
|
+
let accurate: Vec<f64> = bench_pairs
|
|
544
|
+
.iter()
|
|
545
|
+
.map(|(a, b)| accurate_dot(a, b))
|
|
546
|
+
.collect();
|
|
547
|
+
let procedural: Vec<f64> = bench_pairs
|
|
548
|
+
.iter()
|
|
549
|
+
.map(|(a, b)| baseline_dot_procedural(a, b))
|
|
550
|
+
.collect();
|
|
551
|
+
let functional: Vec<f64> = bench_pairs
|
|
552
|
+
.iter()
|
|
553
|
+
.map(|(a, b)| baseline_dot_functional(a, b))
|
|
554
|
+
.collect();
|
|
555
|
+
let unrolled: Vec<f64> = bench_pairs
|
|
556
|
+
.iter()
|
|
557
|
+
.map(|(a, b)| baseline_dot_unrolled(a, b))
|
|
558
|
+
.collect();
|
|
559
|
+
let simsimd: Vec<f64> = bench_pairs
|
|
560
|
+
.iter()
|
|
561
|
+
.map(|(a, b)| SimSIMD::dot(a, b).unwrap())
|
|
562
|
+
.collect();
|
|
563
|
+
|
|
564
|
+
let simsimd_err = calculate_errors(&accurate, &simsimd);
|
|
565
|
+
let procedural_err = calculate_errors(&accurate, &procedural);
|
|
566
|
+
let functional_err = calculate_errors(&accurate, &functional);
|
|
567
|
+
let unrolled_err = calculate_errors(&accurate, &unrolled);
|
|
568
|
+
|
|
569
|
+
let mut group = c.benchmark_group(format!("f32/{}d/dot", dimensions));
|
|
570
|
+
let bytes_per_op = (2 * dimensions * size_of::<f32>() * bench_pairs_count) as u64;
|
|
571
|
+
group.throughput(Throughput::Bytes(bytes_per_op));
|
|
572
|
+
group
|
|
573
|
+
.sample_size(50)
|
|
574
|
+
.measurement_time(Duration::from_secs(2))
|
|
575
|
+
.noise_threshold(0.05);
|
|
576
|
+
|
|
577
|
+
group.bench_function("simsimd", |b| {
|
|
578
|
+
b.iter(|| {
|
|
579
|
+
for (a, b) in &bench_pairs {
|
|
580
|
+
black_box(SimSIMD::dot(a, b));
|
|
581
|
+
}
|
|
582
|
+
})
|
|
583
|
+
});
|
|
584
|
+
print_error(simsimd_err.0, simsimd_err.1);
|
|
585
|
+
|
|
586
|
+
group.bench_function("procedural", |b| {
|
|
587
|
+
b.iter(|| {
|
|
588
|
+
for (a, b) in &bench_pairs {
|
|
589
|
+
black_box(baseline_dot_procedural(a, b));
|
|
590
|
+
}
|
|
591
|
+
})
|
|
592
|
+
});
|
|
593
|
+
print_error(procedural_err.0, procedural_err.1);
|
|
594
|
+
|
|
595
|
+
group.bench_function("functional", |b| {
|
|
596
|
+
b.iter(|| {
|
|
597
|
+
for (a, b) in &bench_pairs {
|
|
598
|
+
black_box(baseline_dot_functional(a, b));
|
|
599
|
+
}
|
|
600
|
+
})
|
|
601
|
+
});
|
|
602
|
+
print_error(functional_err.0, functional_err.1);
|
|
603
|
+
|
|
604
|
+
group.bench_function("unrolled", |b| {
|
|
605
|
+
b.iter(|| {
|
|
606
|
+
for (a, b) in &bench_pairs {
|
|
607
|
+
black_box(baseline_dot_unrolled(a, b));
|
|
608
|
+
}
|
|
609
|
+
})
|
|
610
|
+
});
|
|
611
|
+
print_error(unrolled_err.0, unrolled_err.1);
|
|
612
|
+
group.finish();
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
pub fn f32_l2_benchmark(c: &mut Criterion) {
|
|
616
|
+
let dimensions = get_dense_dimensions();
|
|
617
|
+
let bench_pairs_count = (128 * 1024 * 1024) / (dimensions * size_of::<f32>() * 2);
|
|
618
|
+
let bench_pairs: Vec<_> = (0..bench_pairs_count)
|
|
619
|
+
.map(|i| {
|
|
620
|
+
let mut rng = StdRng::seed_from_u64(1000 + i as u64);
|
|
621
|
+
let a: Vec<f32> = (0..dimensions).map(|_| rng.random()).collect();
|
|
622
|
+
let b: Vec<f32> = (0..dimensions).map(|_| rng.random()).collect();
|
|
623
|
+
(a, b)
|
|
624
|
+
})
|
|
625
|
+
.collect();
|
|
626
|
+
|
|
627
|
+
let accurate: Vec<f64> = bench_pairs.iter().map(|(a, b)| accurate_l2(a, b)).collect();
|
|
628
|
+
let procedural: Vec<f64> = bench_pairs
|
|
629
|
+
.iter()
|
|
630
|
+
.map(|(a, b)| baseline_l2_procedural(a, b))
|
|
631
|
+
.collect();
|
|
632
|
+
let functional: Vec<f64> = bench_pairs
|
|
633
|
+
.iter()
|
|
634
|
+
.map(|(a, b)| baseline_l2_functional(a, b))
|
|
635
|
+
.collect();
|
|
636
|
+
let unrolled: Vec<f64> = bench_pairs
|
|
637
|
+
.iter()
|
|
638
|
+
.map(|(a, b)| baseline_l2_unrolled(a, b))
|
|
639
|
+
.collect();
|
|
640
|
+
let simsimd: Vec<f64> = bench_pairs
|
|
641
|
+
.iter()
|
|
642
|
+
.map(|(a, b)| SimSIMD::euclidean(a, b).unwrap())
|
|
643
|
+
.collect();
|
|
644
|
+
|
|
645
|
+
let simsimd_err = calculate_errors(&accurate, &simsimd);
|
|
646
|
+
let procedural_err = calculate_errors(&accurate, &procedural);
|
|
647
|
+
let functional_err = calculate_errors(&accurate, &functional);
|
|
648
|
+
let unrolled_err = calculate_errors(&accurate, &unrolled);
|
|
649
|
+
|
|
650
|
+
let mut group = c.benchmark_group(format!("f32/{}d/l2", dimensions));
|
|
651
|
+
let bytes_per_op = (2 * dimensions * size_of::<f32>() * bench_pairs_count) as u64;
|
|
652
|
+
group.throughput(Throughput::Bytes(bytes_per_op));
|
|
653
|
+
group
|
|
654
|
+
.sample_size(50)
|
|
655
|
+
.measurement_time(Duration::from_secs(2))
|
|
656
|
+
.noise_threshold(0.05);
|
|
657
|
+
|
|
658
|
+
group.bench_function("simsimd", |b| {
|
|
659
|
+
b.iter(|| {
|
|
660
|
+
for (a, b) in &bench_pairs {
|
|
661
|
+
black_box(SimSIMD::euclidean(a, b));
|
|
662
|
+
}
|
|
663
|
+
})
|
|
664
|
+
});
|
|
665
|
+
print_error(simsimd_err.0, simsimd_err.1);
|
|
666
|
+
|
|
667
|
+
group.bench_function("procedural", |b| {
|
|
668
|
+
b.iter(|| {
|
|
669
|
+
for (a, b) in &bench_pairs {
|
|
670
|
+
black_box(baseline_l2_procedural(a, b));
|
|
671
|
+
}
|
|
672
|
+
})
|
|
673
|
+
});
|
|
674
|
+
print_error(procedural_err.0, procedural_err.1);
|
|
675
|
+
|
|
676
|
+
group.bench_function("functional", |b| {
|
|
677
|
+
b.iter(|| {
|
|
678
|
+
for (a, b) in &bench_pairs {
|
|
679
|
+
black_box(baseline_l2_functional(a, b));
|
|
680
|
+
}
|
|
681
|
+
})
|
|
682
|
+
});
|
|
683
|
+
print_error(functional_err.0, functional_err.1);
|
|
684
|
+
|
|
685
|
+
group.bench_function("unrolled", |b| {
|
|
686
|
+
b.iter(|| {
|
|
687
|
+
for (a, b) in &bench_pairs {
|
|
688
|
+
black_box(baseline_l2_unrolled(a, b));
|
|
689
|
+
}
|
|
690
|
+
})
|
|
691
|
+
});
|
|
692
|
+
print_error(unrolled_err.0, unrolled_err.1);
|
|
693
|
+
group.finish();
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
pub fn i8_cosine_benchmark(c: &mut Criterion) {
|
|
697
|
+
let dimensions = get_dense_dimensions();
|
|
698
|
+
let bench_pairs_count = (128 * 1024 * 1024) / (dimensions * size_of::<i8>() * 2);
|
|
699
|
+
let bench_pairs: Vec<_> = (0..bench_pairs_count)
|
|
700
|
+
.map(|i| {
|
|
701
|
+
let mut rng = StdRng::seed_from_u64(1000 + i as u64);
|
|
702
|
+
let a: Vec<i8> = (0..dimensions).map(|_| rng.random()).collect();
|
|
703
|
+
let b: Vec<i8> = (0..dimensions).map(|_| rng.random()).collect();
|
|
704
|
+
(a, b)
|
|
705
|
+
})
|
|
706
|
+
.collect();
|
|
707
|
+
|
|
708
|
+
let accurate: Vec<f64> = bench_pairs
|
|
709
|
+
.iter()
|
|
710
|
+
.map(|(a, b)| accurate_i8_cos(a, b))
|
|
711
|
+
.collect();
|
|
712
|
+
let procedural: Vec<f64> = bench_pairs
|
|
713
|
+
.iter()
|
|
714
|
+
.map(|(a, b)| baseline_i8_cos_procedural(a, b))
|
|
715
|
+
.collect();
|
|
716
|
+
let simsimd: Vec<f64> = bench_pairs
|
|
717
|
+
.iter()
|
|
718
|
+
.map(|(a, b)| SimSIMD::cosine(a, b).unwrap())
|
|
719
|
+
.collect();
|
|
720
|
+
|
|
721
|
+
let simsimd_err = calculate_errors(&accurate, &simsimd);
|
|
722
|
+
let procedural_err = calculate_errors(&accurate, &procedural);
|
|
723
|
+
|
|
724
|
+
let mut group = c.benchmark_group(format!("i8/{}d/cosine", dimensions));
|
|
725
|
+
let bytes_per_op = (2 * dimensions * size_of::<i8>() * bench_pairs_count) as u64;
|
|
726
|
+
group.throughput(Throughput::Bytes(bytes_per_op));
|
|
727
|
+
group
|
|
728
|
+
.sample_size(50)
|
|
729
|
+
.measurement_time(Duration::from_secs(2))
|
|
730
|
+
.noise_threshold(0.05);
|
|
731
|
+
|
|
732
|
+
group.bench_function("simsimd", |b| {
|
|
733
|
+
b.iter(|| {
|
|
734
|
+
for (a, b) in &bench_pairs {
|
|
735
|
+
black_box(SimSIMD::cosine(a, b));
|
|
736
|
+
}
|
|
737
|
+
})
|
|
738
|
+
});
|
|
739
|
+
print_error(simsimd_err.0, simsimd_err.1);
|
|
740
|
+
|
|
741
|
+
group.bench_function("procedural", |b| {
|
|
742
|
+
b.iter(|| {
|
|
743
|
+
for (a, b) in &bench_pairs {
|
|
744
|
+
black_box(baseline_i8_cos_procedural(a, b));
|
|
745
|
+
}
|
|
746
|
+
})
|
|
747
|
+
});
|
|
748
|
+
print_error(procedural_err.0, procedural_err.1);
|
|
749
|
+
group.finish();
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
pub fn i8_dot_benchmark(c: &mut Criterion) {
|
|
753
|
+
let dimensions = get_dense_dimensions();
|
|
754
|
+
let bench_pairs_count = (128 * 1024 * 1024) / (dimensions * size_of::<i8>() * 2);
|
|
755
|
+
let bench_pairs: Vec<_> = (0..bench_pairs_count)
|
|
756
|
+
.map(|i| {
|
|
757
|
+
let mut rng = StdRng::seed_from_u64(1000 + i as u64);
|
|
758
|
+
let a: Vec<i8> = (0..dimensions).map(|_| rng.random()).collect();
|
|
759
|
+
let b: Vec<i8> = (0..dimensions).map(|_| rng.random()).collect();
|
|
760
|
+
(a, b)
|
|
761
|
+
})
|
|
762
|
+
.collect();
|
|
763
|
+
|
|
764
|
+
let accurate: Vec<f64> = bench_pairs
|
|
765
|
+
.iter()
|
|
766
|
+
.map(|(a, b)| accurate_i8_dot(a, b))
|
|
767
|
+
.collect();
|
|
768
|
+
let procedural: Vec<f64> = bench_pairs
|
|
769
|
+
.iter()
|
|
770
|
+
.map(|(a, b)| baseline_i8_dot_procedural(a, b))
|
|
771
|
+
.collect();
|
|
772
|
+
let simsimd: Vec<f64> = bench_pairs
|
|
773
|
+
.iter()
|
|
774
|
+
.map(|(a, b)| SimSIMD::dot(a, b).unwrap())
|
|
775
|
+
.collect();
|
|
776
|
+
|
|
777
|
+
let simsimd_err = calculate_errors(&accurate, &simsimd);
|
|
778
|
+
let procedural_err = calculate_errors(&accurate, &procedural);
|
|
779
|
+
|
|
780
|
+
let mut group = c.benchmark_group(format!("i8/{}d/dot", dimensions));
|
|
781
|
+
let bytes_per_op = (2 * dimensions * size_of::<i8>() * bench_pairs_count) as u64;
|
|
782
|
+
group.throughput(Throughput::Bytes(bytes_per_op));
|
|
783
|
+
group
|
|
784
|
+
.sample_size(50)
|
|
785
|
+
.measurement_time(Duration::from_secs(2))
|
|
786
|
+
.noise_threshold(0.05);
|
|
787
|
+
|
|
788
|
+
group.bench_function("simsimd", |b| {
|
|
789
|
+
b.iter(|| {
|
|
790
|
+
for (a, b) in &bench_pairs {
|
|
791
|
+
black_box(SimSIMD::dot(a, b));
|
|
792
|
+
}
|
|
793
|
+
})
|
|
794
|
+
});
|
|
795
|
+
print_error(simsimd_err.0, simsimd_err.1);
|
|
796
|
+
|
|
797
|
+
group.bench_function("procedural", |b| {
|
|
798
|
+
b.iter(|| {
|
|
799
|
+
for (a, b) in &bench_pairs {
|
|
800
|
+
black_box(baseline_i8_dot_procedural(a, b));
|
|
801
|
+
}
|
|
802
|
+
})
|
|
803
|
+
});
|
|
804
|
+
print_error(procedural_err.0, procedural_err.1);
|
|
805
|
+
group.finish();
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
pub fn i8_l2_benchmark(c: &mut Criterion) {
|
|
809
|
+
let dimensions = get_dense_dimensions();
|
|
810
|
+
let bench_pairs_count = (128 * 1024 * 1024) / (dimensions * size_of::<i8>() * 2);
|
|
811
|
+
let bench_pairs: Vec<_> = (0..bench_pairs_count)
|
|
812
|
+
.map(|i| {
|
|
813
|
+
let mut rng = StdRng::seed_from_u64(1000 + i as u64);
|
|
814
|
+
let a: Vec<i8> = (0..dimensions).map(|_| rng.random()).collect();
|
|
815
|
+
let b: Vec<i8> = (0..dimensions).map(|_| rng.random()).collect();
|
|
816
|
+
(a, b)
|
|
817
|
+
})
|
|
818
|
+
.collect();
|
|
819
|
+
|
|
820
|
+
let accurate: Vec<f64> = bench_pairs
|
|
821
|
+
.iter()
|
|
822
|
+
.map(|(a, b)| accurate_i8_l2(a, b))
|
|
823
|
+
.collect();
|
|
824
|
+
let procedural: Vec<f64> = bench_pairs
|
|
825
|
+
.iter()
|
|
826
|
+
.map(|(a, b)| baseline_i8_l2_procedural(a, b))
|
|
827
|
+
.collect();
|
|
828
|
+
let simsimd: Vec<f64> = bench_pairs
|
|
829
|
+
.iter()
|
|
830
|
+
.map(|(a, b)| SimSIMD::euclidean(a, b).unwrap())
|
|
831
|
+
.collect();
|
|
832
|
+
|
|
833
|
+
let simsimd_err = calculate_errors(&accurate, &simsimd);
|
|
834
|
+
let procedural_err = calculate_errors(&accurate, &procedural);
|
|
835
|
+
|
|
836
|
+
let mut group = c.benchmark_group(format!("i8/{}d/l2", dimensions));
|
|
837
|
+
let bytes_per_op = (2 * dimensions * size_of::<i8>() * bench_pairs_count) as u64;
|
|
838
|
+
group.throughput(Throughput::Bytes(bytes_per_op));
|
|
839
|
+
group
|
|
840
|
+
.sample_size(50)
|
|
841
|
+
.measurement_time(Duration::from_secs(2))
|
|
842
|
+
.noise_threshold(0.05);
|
|
843
|
+
|
|
844
|
+
group.bench_function("simsimd", |b| {
|
|
845
|
+
b.iter(|| {
|
|
846
|
+
for (a, b) in &bench_pairs {
|
|
847
|
+
black_box(SimSIMD::euclidean(a, b));
|
|
848
|
+
}
|
|
849
|
+
})
|
|
850
|
+
});
|
|
851
|
+
print_error(simsimd_err.0, simsimd_err.1);
|
|
852
|
+
|
|
853
|
+
group.bench_function("procedural", |b| {
|
|
854
|
+
b.iter(|| {
|
|
855
|
+
for (a, b) in &bench_pairs {
|
|
856
|
+
black_box(baseline_i8_l2_procedural(a, b));
|
|
857
|
+
}
|
|
858
|
+
})
|
|
859
|
+
});
|
|
860
|
+
print_error(procedural_err.0, procedural_err.1);
|
|
861
|
+
group.finish();
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// endregion: Benchmark Functions
|
|
865
|
+
|
|
866
|
+
fn custom_criterion() -> Criterion {
|
|
867
|
+
Criterion::default().without_plots().noise_threshold(0.05)
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
criterion_group! {
|
|
871
|
+
name = benches;
|
|
872
|
+
config = custom_criterion();
|
|
873
|
+
targets = f32_cosine_benchmark, f32_dot_benchmark, f32_l2_benchmark,
|
|
874
|
+
i8_cosine_benchmark, i8_dot_benchmark, i8_l2_benchmark
|
|
875
|
+
}
|
|
876
|
+
criterion_main!(benches);
|