const ORDER: usize = 24; const NS: usize = 4;
#[inline(always)]
fn smlawb(a: i32, b: i32, c: i32) -> i32 {
a.wrapping_add((((b as i64) * (c as i16 as i64)) >> 16) as i32)
}
fn shape_scalar(sar: &mut [[i32; NS]], diff: &[i32; NS], warp: i32, ar: &[i16], n_ar: &mut [i32; NS]) {
for k in 0..NS {
let mut tmp2 = smlawb(diff[k], sar[0][k], warp);
let mut tmp1 = smlawb(sar[0][k], sar[1][k].wrapping_sub(tmp2), warp);
sar[0][k] = tmp2;
let mut acc = (ORDER as i32) >> 1;
acc = smlawb(acc, tmp2, ar[0] as i32);
let mut j = 2;
while j < ORDER {
tmp2 = smlawb(sar[j - 1][k], sar[j][k].wrapping_sub(tmp1), warp);
sar[j - 1][k] = tmp1;
acc = smlawb(acc, tmp1, ar[j - 1] as i32);
tmp1 = smlawb(sar[j][k], sar[j + 1][k].wrapping_sub(tmp2), warp);
sar[j][k] = tmp2;
acc = smlawb(acc, tmp2, ar[j] as i32);
j += 2;
}
sar[ORDER - 1][k] = tmp1;
n_ar[k] = smlawb(acc, tmp1, ar[ORDER - 1] as i32);
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn shape_avx2(sar: &mut [[i32; NS]], diff: &[i32; NS], warp: i32, ar: &[i16], n_ar: &mut [i32; NS]) {
use core::arch::x86_64::*;
let wb = _mm256_set1_epi64x(warp as i64);
let asr16 = |x: __m256i| {
let s = _mm256_cmpgt_epi64(_mm256_setzero_si256(), x);
_mm256_or_si256(_mm256_srli_epi64(x, 16), _mm256_slli_epi64(s, 48))
};
let smlawb_v = |a: __m256i, b: __m256i, cb: __m256i| {
_mm256_add_epi64(a, asr16(_mm256_mul_epi32(b, cb)))
};
let ldv = |p: &[i32; NS]| _mm256_cvtepi32_epi64(_mm_loadu_si128(p.as_ptr() as *const __m128i));
let stv = |p: &mut [i32; NS], v: __m256i| {
let g = _mm256_permutevar8x32_epi32(v, _mm256_setr_epi32(0, 2, 4, 6, 0, 2, 4, 6));
_mm_storeu_si128(p.as_mut_ptr() as *mut __m128i, _mm256_castsi256_si128(g));
};
let cbv = |c: i32| _mm256_set1_epi64x(c as i16 as i64);
let vdiff = ldv(diff);
let mut vsar: [__m256i; ORDER] = core::array::from_fn(|j| ldv(&sar[j]));
let mut tmp2 = smlawb_v(vdiff, vsar[0], wb);
let mut tmp1 = smlawb_v(vsar[0], _mm256_sub_epi64(vsar[1], tmp2), wb);
vsar[0] = tmp2;
let mut acc = smlawb_v(_mm256_set1_epi64x(((ORDER as i32) >> 1) as i64), tmp2, cbv(ar[0] as i32));
let mut j = 2;
while j < ORDER {
tmp2 = smlawb_v(vsar[j - 1], _mm256_sub_epi64(vsar[j], tmp1), wb);
vsar[j - 1] = tmp1;
acc = smlawb_v(acc, tmp1, cbv(ar[j - 1] as i32));
tmp1 = smlawb_v(vsar[j], _mm256_sub_epi64(vsar[j + 1], tmp2), wb);
vsar[j] = tmp2;
acc = smlawb_v(acc, tmp2, cbv(ar[j] as i32));
j += 2;
}
vsar[ORDER - 1] = tmp1;
acc = smlawb_v(acc, tmp1, cbv(ar[ORDER - 1] as i32));
for j in 0..ORDER {
stv(&mut sar[j], vsar[j]);
}
stv(n_ar, acc);
}
#[test]
#[ignore]
fn nsq_shape_microbench() {
#[cfg(not(target_arch = "x86_64"))]
{
println!("x86_64 only");
return;
}
#[cfg(target_arch = "x86_64")]
{
if !std::arch::is_x86_feature_detected!("avx2") {
println!("no avx2");
return;
}
let mut s: u64 = 0x1234_5678_9abc_def1;
let mut rng = || {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
(s >> 40) as i32 - (1 << 23)
};
let ar: Vec<i16> = (0..ORDER).map(|_| (rng() >> 8) as i16).collect();
let warp = 13421; let iters = 2_000_000usize;
for _ in 0..1000 {
let mut sar_a = [[0i32; NS]; ORDER];
for row in sar_a.iter_mut() {
for v in row.iter_mut() {
*v = rng() >> 6;
}
}
let mut sar_b = sar_a;
let diff = [rng() >> 6, rng() >> 6, rng() >> 6, rng() >> 6];
let mut na = [0i32; NS];
let mut nb = [0i32; NS];
shape_scalar(&mut sar_a, &diff, warp, &ar, &mut na);
unsafe { shape_avx2(&mut sar_b, &diff, warp, &ar, &mut nb) };
assert_eq!(na, nb, "n_ar mismatch");
assert_eq!(sar_a, sar_b, "sar mismatch");
}
println!("correctness: AVX2 == scalar over 1000 random states ✓");
let bench = |f: &dyn Fn(&mut [[i32; NS]], &[i32; NS], i32, &[i16], &mut [i32; NS])| -> f64 {
let mut best = f64::INFINITY;
for _ in 0..7 {
let mut sar = [[123i32; NS]; ORDER];
let diff = [7, 11, 13, 17];
let mut n_ar = [0i32; NS];
let t0 = std::time::Instant::now();
for _ in 0..iters {
f(&mut sar, &diff, warp, &ar, &mut n_ar);
sar[0][0] = sar[0][0].wrapping_add(n_ar[0] & 1);
}
let dt = t0.elapsed().as_secs_f64();
std::hint::black_box(&sar);
if dt < best {
best = dt;
}
}
best
};
let scalar_t = bench(&shape_scalar);
let avx2_t = bench(&|sar, diff, w, ar, n| unsafe { shape_avx2(sar, diff, w, ar, n) });
println!(
"shape filter ({ORDER} taps, {NS} states, {iters} iters, best-of-7):",
);
println!(" scalar (4 chains): {:.1} ms", scalar_t * 1e3);
println!(" avx2 (i64 lanes) : {:.1} ms ({:.2}x)", avx2_t * 1e3, scalar_t / avx2_t);
}
}