Skip to main content

slain_num/
simd.rs

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}