1#[cfg(target_arch = "x86_64")]
2#[inline]
3fn fast() -> bool {
4 slain_rt::cpu::avx2()
5}
6
7#[must_use]
8pub fn dot(a: &[f64], b: &[f64]) -> f64 {
9 let n = a.len().min(b.len());
10 #[cfg(target_arch = "x86_64")]
11 if fast() {
12 return unsafe { x86::dot(&a[..n], &b[..n]) };
13 }
14 lanes::dot(&a[..n], &b[..n])
15}
16
17pub fn axpy(k: f64, x: &[f64], y: &mut [f64]) {
18 let n = x.len().min(y.len());
19 #[cfg(target_arch = "x86_64")]
20 if fast() {
21 unsafe { x86::axpy(k, &x[..n], &mut y[..n]) };
22 return;
23 }
24 lanes::axpy(k, &x[..n], &mut y[..n]);
25}
26
27#[must_use]
28pub fn sum(a: &[f64]) -> f64 {
29 #[cfg(target_arch = "x86_64")]
30 if fast() {
31 return unsafe { x86::sum(a) };
32 }
33 lanes::sum(a)
34}
35
36#[must_use]
37pub fn sq_dev(a: &[f64], m: f64) -> f64 {
38 #[cfg(target_arch = "x86_64")]
39 if fast() {
40 return unsafe { x86::sq_dev(a, m) };
41 }
42 lanes::sq_dev(a, m)
43}
44
45#[must_use]
46pub fn sq_diff_u16(a: &[u16], b: &[u16]) -> u64 {
47 let n = a.len().min(b.len());
48 #[cfg(target_arch = "x86_64")]
49 if fast() {
50 return unsafe { x86::sq_diff_u16(&a[..n], &b[..n]) };
51 }
52 a[..n].iter().zip(&b[..n]).map(|(&x, &y)| (i64::from(x) - i64::from(y)).pow(2) as u64).sum()
53}
54
55pub fn gram_rhs(cols: &[&[f64]], t: &[f64], gram: &mut [f64], rhs: &mut [f64]) {
56 let k = cols.len();
57 assert!(gram.len() >= k * k && rhs.len() >= k, "gram k*k and rhs k");
58 let n = cols.iter().map(|c| c.len()).chain([t.len()]).min().unwrap_or(0);
59 if k <= 4 {
60 let mut c = [&[][..]; 4];
61 for (d, s) in c.iter_mut().zip(cols) {
62 *d = &s[..n];
63 }
64 let mut g = [0.0; 10];
65 let mut r = [0.0; 4];
66 let t = &t[..n];
67 #[cfg(target_arch = "x86_64")]
68 let done = fast()
69 && unsafe {
70 match k {
71 1 => x86::gram_rhs4::<1>(&c, t, &mut g, &mut r),
72 2 => x86::gram_rhs4::<2>(&c, t, &mut g, &mut r),
73 3 => x86::gram_rhs4::<3>(&c, t, &mut g, &mut r),
74 4 => x86::gram_rhs4::<4>(&c, t, &mut g, &mut r),
75 _ => {}
76 }
77 true
78 };
79 #[cfg(not(target_arch = "x86_64"))]
80 let done = false;
81 if !done {
82 match k {
83 1 => lanes::gram_rhs4::<1>(&c, t, &mut g, &mut r),
84 2 => lanes::gram_rhs4::<2>(&c, t, &mut g, &mut r),
85 3 => lanes::gram_rhs4::<3>(&c, t, &mut g, &mut r),
86 4 => lanes::gram_rhs4::<4>(&c, t, &mut g, &mut r),
87 _ => {}
88 }
89 }
90 let mut idx = 0;
91 for i in 0..k {
92 for j in i..k {
93 gram[i * k + j] = g[idx];
94 gram[j * k + i] = g[idx];
95 idx += 1;
96 }
97 rhs[i] = r[i];
98 }
99 return;
100 }
101 for i in 0..k {
102 for j in i..k {
103 let v = dot(&cols[i][..n], &cols[j][..n]);
104 gram[i * k + j] = v;
105 gram[j * k + i] = v;
106 }
107 rhs[i] = dot(&cols[i][..n], &t[..n]);
108 }
109}
110
111mod lanes {
112 type L = [f64; 4];
113 const Z: L = [0.0; 4];
114
115 #[inline(always)]
116 fn ld(x: &[f64], i: usize) -> L {
117 [x[i], x[i + 1], x[i + 2], x[i + 3]]
118 }
119
120 #[inline(always)]
121 fn add(a: L, b: L) -> L {
122 [a[0] + b[0], a[1] + b[1], a[2] + b[2], a[3] + b[3]]
123 }
124
125 #[inline(always)]
126 fn fma(a: L, b: L, c: L) -> L {
127 [a[0].mul_add(b[0], c[0]), a[1].mul_add(b[1], c[1]), a[2].mul_add(b[2], c[2]), a[3].mul_add(b[3], c[3])]
128 }
129
130 #[inline(always)]
131 fn sub(a: L, m: f64) -> L {
132 [a[0] - m, a[1] - m, a[2] - m, a[3] - m]
133 }
134
135 #[inline(always)]
136 fn hsum(v: L) -> f64 {
137 (v[0] + v[2]) + (v[1] + v[3])
138 }
139
140 #[inline(always)]
141 fn hsum8(s: [L; 8]) -> f64 {
142 let a = add(add(s[0], s[1]), add(s[2], s[3]));
143 let b = add(add(s[4], s[5]), add(s[6], s[7]));
144 hsum(add(a, b))
145 }
146
147 #[inline(always)]
148 fn reduce(a: &[f64], mut s: [L; 8], step: impl Fn(L, usize, L) -> L, tail: impl Fn(usize) -> f64) -> f64 {
149 let n = a.len();
150 let mut i = 0;
151 while i + 32 <= n {
152 for (j, acc) in s.iter_mut().enumerate() {
153 *acc = step(*acc, i + 4 * j, ld(a, i + 4 * j));
154 }
155 i += 32;
156 }
157 let mut j = 0;
158 while i + 4 <= n {
159 s[j] = step(s[j], i, ld(a, i));
160 j += 1;
161 i += 4;
162 }
163 let mut t = hsum8(s);
164 while i < n {
165 t += tail(i);
166 i += 1;
167 }
168 t
169 }
170
171 pub(super) fn dot(a: &[f64], b: &[f64]) -> f64 {
172 reduce(a, [Z; 8], |acc, i, x| fma(x, ld(b, i), acc), |i| a[i] * b[i])
173 }
174
175 pub(super) fn sum(a: &[f64]) -> f64 {
176 reduce(a, [Z; 8], |acc, _, x| add(acc, x), |i| a[i])
177 }
178
179 pub(super) fn sq_dev(a: &[f64], m: f64) -> f64 {
180 reduce(
181 a,
182 [Z; 8],
183 |acc, _, x| {
184 let d = sub(x, m);
185 fma(d, d, acc)
186 },
187 |i| (a[i] - m) * (a[i] - m),
188 )
189 }
190
191 pub(super) fn axpy(k: f64, x: &[f64], y: &mut [f64]) {
192 let n = x.len();
193 let vk = [k; 4];
194 let mut i = 0;
195 while i + 4 <= n {
196 let r = fma(vk, ld(x, i), ld(y, i));
197 y[i..i + 4].copy_from_slice(&r);
198 i += 4;
199 }
200 while i < n {
201 y[i] += k * x[i];
202 i += 1;
203 }
204 }
205
206 pub(super) fn gram_rhs4<const K: usize>(cols: &[&[f64]; 4], t: &[f64], g: &mut [f64; 10], r: &mut [f64; 4]) {
207 let n = t.len();
208 let mut acc = [Z; 10];
209 let mut rh = [Z; 4];
210 let mut i = 0;
211 while i + 4 <= n {
212 let tv = ld(t, i);
213 let mut v = [Z; 4];
214 for c in 0..K {
215 v[c] = ld(cols[c], i);
216 }
217 let mut idx = 0;
218 for a in 0..K {
219 for b in a..K {
220 acc[idx] = fma(v[a], v[b], acc[idx]);
221 idx += 1;
222 }
223 rh[a] = fma(v[a], tv, rh[a]);
224 }
225 i += 4;
226 }
227 let mut idx = 0;
228 for a in 0..K {
229 for b in a..K {
230 let mut s = hsum(acc[idx]);
231 for row in i..n {
232 s += cols[a][row] * cols[b][row];
233 }
234 g[idx] = s;
235 idx += 1;
236 }
237 let mut s = hsum(rh[a]);
238 for row in i..n {
239 s += cols[a][row] * t[row];
240 }
241 r[a] = s;
242 }
243 }
244}
245
246#[cfg(target_arch = "x86_64")]
247mod x86 {
248 use core::arch::x86_64::*;
249
250 #[inline(always)]
251 unsafe fn hsum(v: __m256d) -> f64 {
252 let s = _mm_add_pd(_mm256_castpd256_pd128(v), _mm256_extractf128_pd::<1>(v));
253 _mm_cvtsd_f64(_mm_add_sd(s, _mm_unpackhi_pd(s, s)))
254 }
255
256 #[inline(always)]
257 unsafe fn hsum8(s: [__m256d; 8]) -> f64 {
258 let a = _mm256_add_pd(_mm256_add_pd(s[0], s[1]), _mm256_add_pd(s[2], s[3]));
259 let b = _mm256_add_pd(_mm256_add_pd(s[4], s[5]), _mm256_add_pd(s[6], s[7]));
260 hsum(_mm256_add_pd(a, b))
261 }
262
263 #[target_feature(enable = "avx2,fma")]
264 pub(super) unsafe fn dot(a: &[f64], b: &[f64]) -> f64 {
265 let n = a.len();
266 let (pa, pb) = (a.as_ptr(), b.as_ptr());
267 let mut s = [_mm256_setzero_pd(); 8];
268 let mut i = 0;
269 while i + 32 <= n {
270 s[0] = _mm256_fmadd_pd(_mm256_loadu_pd(pa.add(i)), _mm256_loadu_pd(pb.add(i)), s[0]);
271 s[1] = _mm256_fmadd_pd(_mm256_loadu_pd(pa.add(i + 4)), _mm256_loadu_pd(pb.add(i + 4)), s[1]);
272 s[2] = _mm256_fmadd_pd(_mm256_loadu_pd(pa.add(i + 8)), _mm256_loadu_pd(pb.add(i + 8)), s[2]);
273 s[3] = _mm256_fmadd_pd(_mm256_loadu_pd(pa.add(i + 12)), _mm256_loadu_pd(pb.add(i + 12)), s[3]);
274 s[4] = _mm256_fmadd_pd(_mm256_loadu_pd(pa.add(i + 16)), _mm256_loadu_pd(pb.add(i + 16)), s[4]);
275 s[5] = _mm256_fmadd_pd(_mm256_loadu_pd(pa.add(i + 20)), _mm256_loadu_pd(pb.add(i + 20)), s[5]);
276 s[6] = _mm256_fmadd_pd(_mm256_loadu_pd(pa.add(i + 24)), _mm256_loadu_pd(pb.add(i + 24)), s[6]);
277 s[7] = _mm256_fmadd_pd(_mm256_loadu_pd(pa.add(i + 28)), _mm256_loadu_pd(pb.add(i + 28)), s[7]);
278 i += 32;
279 }
280 let mut j = 0;
281 while i + 4 <= n {
282 s[j] = _mm256_fmadd_pd(_mm256_loadu_pd(pa.add(i)), _mm256_loadu_pd(pb.add(i)), s[j]);
283 j += 1;
284 i += 4;
285 }
286 let mut t = hsum8(s);
287 while i < n {
288 t += a[i] * b[i];
289 i += 1;
290 }
291 t
292 }
293
294 #[target_feature(enable = "avx2,fma")]
295 pub(super) unsafe fn axpy(k: f64, x: &[f64], y: &mut [f64]) {
296 let n = x.len();
297 let vk = _mm256_set1_pd(k);
298 let mut i = 0;
299 while i + 4 <= n {
300 let r = _mm256_fmadd_pd(vk, _mm256_loadu_pd(x.as_ptr().add(i)), _mm256_loadu_pd(y.as_ptr().add(i)));
301 _mm256_storeu_pd(y.as_mut_ptr().add(i), r);
302 i += 4;
303 }
304 while i < n {
305 y[i] += k * x[i];
306 i += 1;
307 }
308 }
309
310 #[target_feature(enable = "avx2")]
311 pub(super) unsafe fn sum(a: &[f64]) -> f64 {
312 let n = a.len();
313 let p = a.as_ptr();
314 let mut s = [_mm256_setzero_pd(); 8];
315 let mut i = 0;
316 while i + 32 <= n {
317 for (j, acc) in s.iter_mut().enumerate() {
318 *acc = _mm256_add_pd(*acc, _mm256_loadu_pd(p.add(i + 4 * j)));
319 }
320 i += 32;
321 }
322 let mut j = 0;
323 while i + 4 <= n {
324 s[j] = _mm256_add_pd(s[j], _mm256_loadu_pd(p.add(i)));
325 j += 1;
326 i += 4;
327 }
328 let mut t = hsum8(s);
329 while i < n {
330 t += a[i];
331 i += 1;
332 }
333 t
334 }
335
336 #[target_feature(enable = "avx2,fma")]
337 pub(super) unsafe fn sq_dev(a: &[f64], m: f64) -> f64 {
338 let n = a.len();
339 let p = a.as_ptr();
340 let vm = _mm256_set1_pd(m);
341 let mut s = [_mm256_setzero_pd(); 8];
342 let mut i = 0;
343 while i + 32 <= n {
344 for (j, acc) in s.iter_mut().enumerate() {
345 let d = _mm256_sub_pd(_mm256_loadu_pd(p.add(i + 4 * j)), vm);
346 *acc = _mm256_fmadd_pd(d, d, *acc);
347 }
348 i += 32;
349 }
350 let mut j = 0;
351 while i + 4 <= n {
352 let d = _mm256_sub_pd(_mm256_loadu_pd(p.add(i)), vm);
353 s[j] = _mm256_fmadd_pd(d, d, s[j]);
354 j += 1;
355 i += 4;
356 }
357 let mut t = hsum8(s);
358 while i < n {
359 t += (a[i] - m) * (a[i] - m);
360 i += 1;
361 }
362 t
363 }
364
365 #[target_feature(enable = "avx2")]
366 pub(super) unsafe fn sq_diff_u16(a: &[u16], b: &[u16]) -> u64 {
367 let n = a.len();
368 let zero = _mm256_setzero_si256();
369 let (mut acc0, mut acc1) = (zero, zero);
370 let mut i = 0;
371 while i + 16 <= n {
372 let pa = _mm256_loadu_si256(a.as_ptr().add(i).cast());
373 let pb = _mm256_loadu_si256(b.as_ptr().add(i).cast());
374 if _mm256_movemask_epi8(_mm256_or_si256(pa, pb)) & 0xAAAA_AAAAu32 as i32 == 0 {
375 let d = _mm256_sub_epi16(pa, pb);
376 let sq = _mm256_madd_epi16(d, d);
377 acc0 = _mm256_add_epi64(acc0, _mm256_unpacklo_epi32(sq, zero));
378 acc1 = _mm256_add_epi64(acc1, _mm256_unpackhi_epi32(sq, zero));
379 } else {
380 let lo = _mm256_sub_epi32(_mm256_unpacklo_epi16(pa, zero), _mm256_unpacklo_epi16(pb, zero));
381 let hi = _mm256_sub_epi32(_mm256_unpackhi_epi16(pa, zero), _mm256_unpackhi_epi16(pb, zero));
382 let odd = |v: __m256i| _mm256_srli_epi64::<32>(v);
383 acc0 = _mm256_add_epi64(acc0, _mm256_add_epi64(_mm256_mul_epi32(lo, lo), _mm256_mul_epi32(odd(lo), odd(lo))));
384 acc1 = _mm256_add_epi64(acc1, _mm256_add_epi64(_mm256_mul_epi32(hi, hi), _mm256_mul_epi32(odd(hi), odd(hi))));
385 }
386 i += 16;
387 }
388 let mut lanes = [0u64; 4];
389 _mm256_storeu_si256(lanes.as_mut_ptr().cast(), _mm256_add_epi64(acc0, acc1));
390 let mut total: u64 = lanes.iter().sum();
391 while i < n {
392 total += (i64::from(a[i]) - i64::from(b[i])).pow(2) as u64;
393 i += 1;
394 }
395 total
396 }
397
398 #[target_feature(enable = "avx2,fma")]
399 pub(super) unsafe fn gram_rhs4<const K: usize>(cols: &[&[f64]; 4], t: &[f64], g: &mut [f64; 10], r: &mut [f64; 4]) {
400 let n = t.len();
401 let z = _mm256_setzero_pd();
402 let mut acc = [z; 10];
403 let mut rh = [z; 4];
404 let p: [*const f64; 4] = [cols[0].as_ptr(), cols[1].as_ptr(), cols[2].as_ptr(), cols[3].as_ptr()];
405 let mut i = 0;
406 while i + 4 <= n {
407 let tv = _mm256_loadu_pd(t.as_ptr().add(i));
408 let mut v = [z; 4];
409 for c in 0..K {
410 v[c] = _mm256_loadu_pd(p[c].add(i));
411 }
412 let mut idx = 0;
413 for a in 0..K {
414 for b in a..K {
415 acc[idx] = _mm256_fmadd_pd(v[a], v[b], acc[idx]);
416 idx += 1;
417 }
418 rh[a] = _mm256_fmadd_pd(v[a], tv, rh[a]);
419 }
420 i += 4;
421 }
422 let k = K;
423 let mut idx = 0;
424 for a in 0..k {
425 for b in a..k {
426 let mut s = hsum(acc[idx]);
427 for row in i..n {
428 s += cols[a][row] * cols[b][row];
429 }
430 g[idx] = s;
431 idx += 1;
432 }
433 let mut s = hsum(rh[a]);
434 for row in i..n {
435 s += cols[a][row] * t[row];
436 }
437 r[a] = s;
438 }
439 }
440}
441
442#[cfg(test)]
443mod tests {
444 use super::*;
445
446 fn close(a: f64, b: f64) -> bool {
447 (a - b).abs() <= 1e-9 * (1.0 + a.abs().max(b.abs()))
448 }
449
450 #[test]
451 fn kernels_match_scalar() {
452 for len in [0usize, 1, 3, 4, 7, 31, 32, 33, 100, 1003] {
453 let a: Vec<f64> = (0..len).map(|i| (i as f64 * 0.37).sin()).collect();
454 let b: Vec<f64> = (0..len).map(|i| (i as f64 * 0.11).cos()).collect();
455 let want: f64 = a.iter().zip(&b).map(|(x, y)| x * y).sum();
456 assert!(close(dot(&a, &b), want), "dot len {len}");
457 assert!(close(sum(&a), a.iter().sum::<f64>()), "sum len {len}");
458 let m = 0.25;
459 assert!(close(sq_dev(&a, m), a.iter().map(|v| (v - m) * (v - m)).sum()), "sq_dev len {len}");
460 let mut y = b.clone();
461 axpy(2.5, &a, &mut y);
462 assert!(y.iter().zip(a.iter().zip(&b)).all(|(o, (x, z))| (o - (z + 2.5 * x)).abs() < 1e-12));
463 }
464 for (len, top) in [(1037usize, 65536u32), (1037, 32768), (1037, 4096), (15, 1024), (16, 32768), (40, 65536)] {
465 let p: Vec<u16> = (0..len).map(|i| (i as u32 * 7919 % top) as u16).collect();
466 let q: Vec<u16> = (0..len).map(|i| (i as u32 * 104_729 % top) as u16).collect();
467 let want: u64 = p.iter().zip(&q).map(|(&x, &y)| (i64::from(x) - i64::from(y)).pow(2) as u64).sum();
468 assert_eq!(sq_diff_u16(&p, &q), want, "sq_diff len {len} range {top}");
469 assert_eq!(sq_diff_u16(&p, &q), crate::simd_prev::sq_diff_u16(&p, &q));
470 }
471 }
472
473 #[cfg(target_arch = "x86_64")]
474 #[test]
475 fn lanes_equal_the_x86_kernels_bitwise() {
476 if !fast() {
477 return;
478 }
479 for len in [0usize, 1, 3, 4, 7, 31, 32, 33, 100, 1003] {
480 let a: Vec<f64> = (0..len).map(|i| (i as f64 * 0.37).sin()).collect();
481 let b: Vec<f64> = (0..len).map(|i| (i as f64 * 0.11).cos()).collect();
482 unsafe {
483 assert_eq!(lanes::dot(&a, &b).to_bits(), x86::dot(&a, &b).to_bits(), "dot len {len}");
484 assert_eq!(lanes::sum(&a).to_bits(), x86::sum(&a).to_bits(), "sum len {len}");
485 assert_eq!(lanes::sq_dev(&a, 0.25).to_bits(), x86::sq_dev(&a, 0.25).to_bits(), "sq_dev len {len}");
486 let (mut y0, mut y1) = (b.clone(), b.clone());
487 lanes::axpy(2.5, &a, &mut y0);
488 x86::axpy(2.5, &a, &mut y1);
489 assert!(y0.iter().zip(&y1).all(|(p, q)| p.to_bits() == q.to_bits()), "axpy len {len}");
490 let c = [&a[..], &b[..], &a[..], &b[..]];
491 let (mut g0, mut r0, mut g1, mut r1) = ([0.0; 10], [0.0; 4], [0.0; 10], [0.0; 4]);
492 lanes::gram_rhs4::<3>(&c, &b, &mut g0, &mut r0);
493 x86::gram_rhs4::<3>(&c, &b, &mut g1, &mut r1);
494 assert_eq!(g0.map(f64::to_bits), g1.map(f64::to_bits), "gram len {len}");
495 assert_eq!(r0.map(f64::to_bits), r1.map(f64::to_bits), "rhs len {len}");
496 }
497 }
498 }
499
500 #[test]
501 fn gram_rhs_matches_dots() {
502 for k in 0..=6usize {
503 for len in [0usize, 3, 4, 17, 4096] {
504 let cols: Vec<Vec<f64>> = (0..k).map(|c| (0..len).map(|i| ((i * (c + 3)) as f64 * 0.013).sin()).collect()).collect();
505 let t: Vec<f64> = (0..len).map(|i| (i as f64 * 0.007).cos()).collect();
506 let refs: Vec<&[f64]> = cols.iter().map(Vec::as_slice).collect();
507 let (mut g, mut r) = (vec![0.0; k * k], vec![0.0; k]);
508 gram_rhs(&refs, &t, &mut g, &mut r);
509 for i in 0..k {
510 for j in 0..k {
511 let want: f64 = cols[i].iter().zip(&cols[j]).map(|(x, y)| x * y).sum();
512 assert!(close(g[i * k + j], want), "k {k} len {len} g[{i}][{j}]");
513 }
514 let want: f64 = cols[i].iter().zip(&t).map(|(x, y)| x * y).sum();
515 assert!(close(r[i], want), "k {k} len {len} rhs[{i}]");
516 }
517 }
518 }
519 }
520
521 #[test]
522 #[ignore = "bench"]
523 fn simd_bench() {
524 use crate::bench::{slain, Harness};
525 use crate::simd_prev as prev;
526 let mut h = Harness::default();
527 let n = 4096;
528 let a: Vec<f64> = (0..n).map(|i| (i as f64 * 0.37).sin()).collect();
529 let b: Vec<f64> = (0..n).map(|i| (i as f64 * 0.11).cos()).collect();
530 let p: Vec<u16> = (0..n).map(|i| (i * 7919 % 1024) as u16).collect();
531 let q: Vec<u16> = (0..n).map(|i| (i * 104_729 % 1024) as u16).collect();
532 let cols: Vec<Vec<f64>> = (0..3).map(|c| (0..n).map(|i| ((i * (c + 3)) as f64 * 0.013).sin()).collect()).collect();
533 let refs: Vec<&[f64]> = cols.iter().map(Vec::as_slice).collect();
534 let mut g = h.benchmark_group("dot 4096 f64");
535 g.bench_function("previous", |bn| bn.iter(|| prev::dot(slain(&a), slain(&b))));
536 g.bench_function("register-mapped", |bn| bn.iter(|| dot(slain(&a), slain(&b))));
537 g.finish();
538 let mut g = h.benchmark_group("sum 4096 f64");
539 g.bench_function("previous", |bn| bn.iter(|| prev::sum(slain(&a))));
540 g.bench_function("register-mapped", |bn| bn.iter(|| sum(slain(&a))));
541 g.finish();
542 let mut g = h.benchmark_group("sq_diff_u16 4096 (10-bit)");
543 g.bench_function("previous", |bn| bn.iter(|| prev::sq_diff_u16(slain(&p), slain(&q))));
544 g.bench_function("register-mapped", |bn| bn.iter(|| sq_diff_u16(slain(&p), slain(&q))));
545 g.finish();
546 let mut g = h.benchmark_group("gram + rhs, 3 columns x 4096 (Wiener fit)");
547 g.bench_function("previous (9 dots)", |bn| {
548 bn.iter(|| {
549 let mut o = [0.0; 12];
550 for i in 0..3 {
551 for j in i..3 {
552 o[i * 3 + j] = prev::dot(slain(refs[i]), slain(refs[j]));
553 }
554 o[9 + i] = prev::dot(slain(refs[i]), slain(&a));
555 }
556 o
557 })
558 });
559 g.bench_function("register-mapped (one pass)", |bn| {
560 bn.iter(|| {
561 let (mut gm, mut r) = ([0.0; 9], [0.0; 3]);
562 gram_rhs(slain(&refs), slain(&a), &mut gm, &mut r);
563 (gm, r)
564 })
565 });
566 g.finish();
567 }
568}