#![allow(unknown_lints, clippy::chunks_exact_to_as_chunks)]
use std::hint::black_box;
use std::time::{Duration, Instant};
use fgf::poly::AES;
use fgf::{
Binary, Elem, Goldilocks, Mersenne31, Polynomial, QuadMersenne31, RS, Tower, TowerSpec,
backend, backend_for, ops,
};
type Gf16 = fgf::Binary<16, fgf::Tower<fgf::Rijndael16>>;
type Gf32 = fgf::Binary<32, fgf::Tower<fgf::Rijndael32>>;
type Gf64 = fgf::Binary<64, fgf::Tower<fgf::Rijndael64>>;
type FanPaar16Field = fgf::Binary<16, fgf::Tower<fgf::FanPaar16>>;
type FanPaar32Field = fgf::Binary<32, fgf::Tower<fgf::FanPaar32>>;
type FanPaar64Field = fgf::Binary<64, fgf::Tower<fgf::FanPaar64>>;
fn noise(len: usize, seed: u64) -> Vec<u8> {
let mut state = seed | 1;
(0..len)
.map(|_| {
state = state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1);
(state >> 33) as u8
})
.collect()
}
fn fmt_ns(ns: f64) -> String {
if ns < 1_000.0 {
format!("{ns:.2}ns")
} else if ns < 1_000_000.0 {
format!("{:.2}µs", ns / 1_000.0)
} else {
format!("{:.2}ms", ns / 1_000_000.0)
}
}
const BATCH_TARGET_NS: f64 = 100_000.0;
const MAX_BATCH_REPS: u64 = 1 << 20;
const CONTROL_BYTES: usize = 256 * 1024;
const SCALAR_ITERS: usize = 1 << 20;
fn batch_reps(est_ns: f64, min_reps: u64) -> u64 {
((BATCH_TARGET_NS / est_ns.max(1.0)).ceil() as u64).clamp(min_reps, MAX_BATCH_REPS)
}
fn control_body(dst: &mut [u8], src: &[u8]) {
let dst = black_box(dst);
let src = black_box(src);
for (d, s) in dst.iter_mut().zip(src) {
*d ^= s;
}
}
fn print_host_lines() {
let status = std::fs::read_to_string("/proc/self/status").ok();
let cpus = status
.as_deref()
.and_then(|text| {
text.lines()
.find(|line| line.starts_with("Cpus_allowed_list:"))
})
.map(|line| line["Cpus_allowed_list:".len()..].trim().to_string());
let stat = std::fs::read_to_string("/proc/self/stat").ok();
let policy = stat.as_deref().and_then(|text| {
text.rsplit_once(')')?
.1
.split_whitespace()
.nth(38)
.and_then(|field| field.parse::<u32>().ok())
});
let policy_name = match policy {
Some(0) => "normal",
Some(1) => "fifo",
Some(2) => "rr",
Some(3) => "batch",
Some(5) => "idle",
Some(6) => "deadline",
Some(_) => "unknown",
None => "unavailable",
};
println!(
"HOST\tcpus_allowed_list={}\tsched_policy={} {policy_name}",
cpus.as_deref().unwrap_or("unavailable"),
policy.map_or("n/a".to_string(), |p| p.to_string()),
);
}
fn print_field_backends() {
fn field<F: fgf::FieldKernels>(label: &str) {
println!("FIELD_BACKEND\t{label}\t{}", backend_for::<F>().name());
}
field::<Binary<8, Polynomial<AES>>>("gf8b");
field::<Binary<8, Polynomial<RS>>>("gf8d");
field::<Gf16>("gf16b");
field::<Gf32>("gf32b");
field::<Gf64>("gf64b");
field::<FanPaar16Field>("fp16");
field::<FanPaar32Field>("fp32");
field::<FanPaar64Field>("fp64");
field::<Mersenne31>("m31");
field::<QuadMersenne31>("qm31");
field::<Goldilocks>("gld");
field::<Gf16d>("gf16d");
}
const M31_PRIME: u32 = 0x7fff_ffff;
const GOLDILOCKS_PRIME: u64 = 0xffff_ffff_0000_0001;
fn canonicalize_m31_lanes(buf: &mut [u8]) {
for lane in buf.chunks_exact_mut(4) {
let value = u32::from_le_bytes(lane.try_into().unwrap()) % M31_PRIME;
lane.copy_from_slice(&value.to_le_bytes());
}
}
fn canonicalize_goldilocks_lanes(buf: &mut [u8]) {
for lane in buf.chunks_exact_mut(8) {
let value = u64::from_le_bytes(lane.try_into().unwrap()) % GOLDILOCKS_PRIME;
lane.copy_from_slice(&value.to_le_bytes());
}
}
fn bench(label: &str, bytes: usize, mut body: impl FnMut()) -> f64 {
for _ in 0..16 {
body();
}
let reps = batch_reps(
{
let start = Instant::now();
body();
start.elapsed().as_secs_f64() * 1e9
},
32,
);
let mut samples = Vec::with_capacity(64);
let deadline = Instant::now() + Duration::from_millis(250);
while Instant::now() < deadline && samples.len() < 64 {
let start = Instant::now();
for _ in 0..reps {
body();
}
samples.push(start.elapsed().as_secs_f64() * 1e9 / reps as f64);
}
samples.sort_unstable_by(f64::total_cmp);
let median = samples[samples.len() / 2];
let gib_per_sec = bytes as f64 / (median / 1e9) / (1024.0 * 1024.0 * 1024.0);
println!(
" {label:<44} {:>9} {gib_per_sec:>7.2} GiB/s",
fmt_ns(median)
);
println!("SAMPLE\t{label}\t{bytes}\t{median:.9}");
median
}
const CROSSOVER_LENGTHS: &[usize] = &[
16, 32, 64, 128, 256, 512, 1_024, 2_048, 4_096, 16_384, 65_536,
];
fn bench_preparation_crossover() {
println!("preparation crossover — one-shot vs prepared, by row length:");
for &len in CROSSOVER_LENGTHS {
let src = noise(len, 0xa00 + len as u64);
let mut dst = noise(len, 0xb00 + len as u64);
let coeff8 = Elem::<Binary<8, Polynomial<AES>>>::from_raw(0x53);
let coeff16 = Elem::<Gf16>::from_raw(0x53a7);
let prepared8 = ops::Coeff::<Binary<8, Polynomial<AES>>>::new(coeff8);
let prepared16 = ops::Coeff::<Gf16>::new(coeff16);
println!(" row {len} B:");
let one8 = bench(" mul_add one-shot gf8b", len, || {
ops::mul_add::<Binary<8, Polynomial<AES>>>(
black_box(&mut dst),
coeff8,
black_box(&src),
);
});
let with8 = bench(" mul_add prepared gf8b", len, || {
ops::mul_add_with::<Binary<8, Polynomial<AES>>>(
black_box(&mut dst),
&prepared8,
black_box(&src),
);
});
let one16 = bench(" mul_add one-shot gf16b", len, || {
ops::mul_add::<Gf16>(black_box(&mut dst), coeff16, black_box(&src));
});
let with16 = bench(" mul_add prepared gf16b", len, || {
ops::mul_add_with::<Gf16>(black_box(&mut dst), &prepared16, black_box(&src));
});
println!(
" one-shot/prepared: gf8b {:.2}x gf16b {:.2}x",
one8 / with8,
one16 / with16
);
}
println!();
}
const NETWORK_LENGTHS: &[usize] = &[
64, 256, 512, 1_152, 1_168, 1_184, 1_200, 1_216, 1_232, 1_248, 1_400,
];
fn bench_network_payloads() {
println!("network-size GF(2^8) payloads:");
for &len in NETWORK_LENGTHS {
println!(" payload {len} B:");
let src = noise(len, 0x700 + len as u64);
let mut dst = noise(len, 0x800 + len as u64);
bench("xor", len, || {
ops::add_assign::<Binary<8, Polynomial<AES>>>(black_box(&mut dst), black_box(&src));
});
bench("mul_add", len, || {
ops::mul_add::<Binary<8, Polynomial<AES>>>(
black_box(&mut dst),
Elem::<Binary<8, Polynomial<AES>>>::from_raw(0x53),
black_box(&src),
);
});
bench("mul_assign", len, || {
ops::mul_assign::<Binary<8, Polynomial<AES>>>(
black_box(&mut dst),
Elem::<Binary<8, Polynomial<AES>>>::from_raw(0x53),
);
});
for nrows in [4usize, 16] {
let coeffs: Vec<_> = (0..nrows)
.map(|row| {
Elem::<Binary<8, Polynomial<AES>>>::from_raw(
(row as u8).wrapping_mul(37).wrapping_add(2),
)
})
.collect();
let mut rows = noise(len * nrows, 0x900 + nrows as u64);
let label = format!("scatter ({nrows} rows)");
bench(&label, len * nrows, || {
ops::mul_add_scatter::<Binary<8, Polynomial<AES>>>(
black_box(&mut rows),
len,
black_box(&coeffs),
black_box(&src),
);
});
}
}
println!();
}
fn bench_network(label: &str, bytes: usize, mut body: impl FnMut()) -> f64 {
for _ in 0..64 {
body();
}
let reps = batch_reps(
{
let start = Instant::now();
body();
start.elapsed().as_secs_f64() * 1e9
},
512,
);
let mut samples = [0.0; 64];
for sample in &mut samples {
let start = Instant::now();
for _ in 0..reps {
body();
}
*sample = start.elapsed().as_secs_f64() * 1e9 / reps as f64;
}
samples.sort_unstable_by(f64::total_cmp);
let median = samples[samples.len() / 2];
println!(
" {label:<44} {:>9} {:>7.2} GiB/s",
fmt_ns(median),
bytes as f64 / (median / 1e9) / (1024.0 * 1024.0 * 1024.0)
);
println!("SAMPLE\t{label}\t{bytes}\t{median:.9}");
median
}
fn bench_scalar(label: &str, mut body: impl FnMut()) {
for _ in 0..4 {
body();
}
let mut samples = Vec::with_capacity(64);
let deadline = Instant::now() + Duration::from_millis(250);
while Instant::now() < deadline && samples.len() < 64 {
let start = Instant::now();
body();
samples.push(start.elapsed());
}
samples.sort_unstable();
let median = samples[samples.len() / 2];
let ns = median.as_secs_f64() * 1e9 / SCALAR_ITERS as f64;
let mops = SCALAR_ITERS as f64 / median.as_secs_f64() / 1e6;
println!(" {label:<44} {ns:>9.2} ns/op {mops:>7.2} Mops/s");
println!("SCALAR\t{label}\t{ns:.9}");
}
fn bench_network_alignment() {
for &len in &[1152usize, 1168, 1184, 1200] {
let src_storage = noise(len + 128, 0x700 + len as u64);
let mut dst_storage = noise(len + 128, 0x800 + len as u64);
for &off in &[0usize, 16, 32, 48] {
let s = src_storage.as_ptr().align_offset(64) + off;
let d = dst_storage.as_ptr().align_offset(64) + off;
let src = &src_storage[s..s + len];
let dst = &mut dst_storage[d..d + len];
println!(
"network diagnostic {len} B off={off} dst={}",
(dst.as_ptr() as usize) & 63
);
bench_network("xor", len, || {
ops::add_assign::<Binary<8, Polynomial<AES>>>(black_box(&mut *dst), black_box(src))
});
bench_network("mul_add", len, || {
ops::mul_add::<Binary<8, Polynomial<AES>>>(
black_box(&mut *dst),
Elem::<Binary<8, Polynomial<AES>>>::from_raw(0x53),
black_box(src),
)
});
bench_network("mul_assign", len, || {
ops::mul_assign::<Binary<8, Polynomial<AES>>>(
black_box(&mut *dst),
Elem::<Binary<8, Polynomial<AES>>>::from_raw(0x53),
)
});
for nrows in [4usize, 16] {
let coeffs: Vec<_> = (0..nrows)
.map(|row| {
Elem::<Binary<8, Polynomial<AES>>>::from_raw(
(row as u8).wrapping_mul(37).wrapping_add(2),
)
})
.collect();
let mut storage = noise(len * nrows + 128, 0x900 + nrows as u64);
let r = storage.as_ptr().align_offset(64) + off;
println!(
"rows={nrows} base={}",
(storage[r..].as_ptr() as usize) & 63
);
let rows = &mut storage[r..r + len * nrows];
bench_network(&format!("scatter {nrows}"), len * nrows, || {
ops::mul_add_scatter::<Binary<8, Polynomial<AES>>>(
black_box(&mut *rows),
len,
black_box(&coeffs),
black_box(src),
)
});
}
}
}
}
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
fn bench_blocked_vs_axpy() {
use fgf::internals::kernel::tables::{TowerCoeff, TowerTables};
use fgf::internals::kernel::x86;
use fgf::internals::kernel::{SimdToken, X64V2Token, X64V3GfniCryptoToken};
let Some(ssse3) = X64V2Token::summon() else {
return;
};
let gfni = X64V3GfniCryptoToken::summon();
println!("blocked vs AXPY — direct GF(2^16) kernel calls (dispatch bypassed):");
for &row_len in &[4 * 1024usize, 16 * 1024, 64 * 1024] {
for &nsrc in &[2usize, 4, 8, 16] {
let sources: Vec<Vec<u8>> = (0..nsrc)
.map(|t| noise(row_len, 0xc00 + t as u64))
.collect();
let srcs: Vec<&[u8]> = sources.iter().map(Vec::as_slice).collect();
let coeffs: Vec<Elem<Gf16>> = (0..nsrc)
.map(|t| Elem::<Gf16>::from_raw(((t * 7919) as u16).wrapping_add(3)))
.collect();
let mut dst = noise(row_len, 0xd00);
let traffic = row_len * nsrc;
println!(" gather {nsrc} sources x {} KiB:", row_len / 1024);
let blocked = bench(" gather blocked ssse3", traffic, || {
x86::gf16::mul_add_gather_ssse3(
ssse3,
black_box(&mut dst),
&coeffs,
black_box(&srcs),
);
});
let axpy = bench(" gather AXPY ssse3", traffic, || {
for (&coeff, &src) in coeffs.iter().zip(&srcs) {
x86::gf16::mul_add_ssse3(
ssse3,
black_box(&mut dst),
&TowerTables::new(coeff),
black_box(src),
);
}
});
println!(" ssse3 blocked/AXPY: {:.2}x", axpy / blocked);
if let Some(gfni) = gfni {
let blocked = bench(" gather blocked gfni", traffic, || {
x86::gf16::mul_add_gather_gfni(
gfni,
black_box(&mut dst),
&coeffs,
black_box(&srcs),
);
});
let axpy = bench(" gather AXPY gfni", traffic, || {
for (&coeff, &src) in coeffs.iter().zip(&srcs) {
x86::gf16::mul_add_gfni(
gfni,
black_box(&mut dst),
TowerCoeff::new(coeff),
black_box(src),
);
}
});
println!(" gfni blocked/AXPY: {:.2}x", axpy / blocked);
}
}
}
println!();
}
const LARGE_DST_LENGTHS: &[usize] = &[1 << 20, 8 << 20, 32 << 20];
fn bench_large_destination() {
println!("large destinations — mul_into store policy:");
for &len in LARGE_DST_LENGTHS {
let src = noise(len, 0xd00);
let mut dst = noise(len, 0xd01);
let mib = len / (1024 * 1024);
bench(&format!("{mib:3} MiB mul_into gf8b"), len, || {
ops::mul_into::<Binary<8, Polynomial<AES>>>(
black_box(&mut dst),
Elem::<Binary<8, Polynomial<AES>>>::from_raw(0x53),
black_box(&src),
);
});
bench(&format!("{mib:3} MiB mul_into gf16b"), len, || {
ops::mul_into::<Gf16>(
black_box(&mut dst),
Elem::<Gf16>::from_raw(0x53a7),
black_box(&src),
);
});
bench(&format!("{mib:3} MiB mul_into+read gf8b"), len, || {
ops::mul_into::<Binary<8, Polynomial<AES>>>(
black_box(&mut dst),
Elem::<Binary<8, Polynomial<AES>>>::from_raw(0x53),
black_box(&src),
);
let mut acc = 0u64;
for word in dst.chunks_exact(8) {
acc ^= u64::from_le_bytes(word.try_into().unwrap());
}
black_box(acc);
});
bench(&format!("{mib:3} MiB mul_add gf8b"), len, || {
ops::mul_add::<Binary<8, Polynomial<AES>>>(
black_box(&mut dst),
Elem::<Binary<8, Polynomial<AES>>>::from_raw(0x53),
black_box(&src),
);
});
}
println!();
}
fn bench_destination_alignment() {
println!("destination alignment — multi-row scatter:");
let nrows = 8;
for &row_len in &[64 * 1024usize, 256 * 1024] {
let src = noise(row_len, 0xe00);
let coeffs8: Vec<_> = (0..nrows)
.map(|j| {
Elem::<Binary<8, Polynomial<AES>>>::from_raw(
(j as u8).wrapping_mul(37).wrapping_add(2),
)
})
.collect();
let coeffs16: Vec<_> = (0..nrows)
.map(|j| Elem::<Gf16>::from_raw((j as u16).wrapping_mul(9871).wrapping_add(2)))
.collect();
let mut backing = noise(row_len * nrows + 79, 0xe01);
let aligned = backing.as_ptr().align_offset(64);
let kib = row_len / 1024;
for skew in [0usize, 16] {
let start = aligned + skew;
let rows = &mut backing[start..start + row_len * nrows];
assert_eq!(rows.as_ptr() as usize % 64, skew);
let traffic = row_len * nrows;
bench(
&format!("{kib:4} KiB rows, skew {skew:2} gf8b"),
traffic,
|| {
ops::mul_add_scatter::<Binary<8, Polynomial<AES>>>(
black_box(rows),
row_len,
&coeffs8,
black_box(&src),
);
},
);
bench(
&format!("{kib:4} KiB rows, skew {skew:2} gf16b"),
traffic,
|| {
ops::mul_add_scatter::<Gf16>(
black_box(rows),
row_len,
&coeffs16,
black_box(&src),
);
},
);
}
}
println!();
}
const SMALL_ROW_LENGTHS: &[usize] = &[64, 256, 1_024, 16_384];
fn bench_small_row_shapes() {
println!("small-row GF(2^16) multi-row shapes — preparation-dominated:");
let nrows = 16;
let nsrc = 8;
for &row_len in SMALL_ROW_LENGTHS {
let sources: Vec<Vec<u8>> = (0..nsrc)
.map(|t| noise(row_len, 0xf00 + t as u64))
.collect();
let srcs: Vec<&[u8]> = sources.iter().map(Vec::as_slice).collect();
let coeff_at = |i: usize| match i % 5 {
0 => Elem::<Gf16>::ZERO,
1 => Elem::<Gf16>::ONE,
_ => Elem::<Gf16>::from_raw(((i * 7919) as u16) | 0x0100),
};
let row_coeffs: Vec<Elem<Gf16>> = (0..nrows).map(coeff_at).collect();
let coeff_sets: Vec<Vec<Elem<Gf16>>> = (0..nsrc)
.map(|t| (0..nrows).map(|j| coeff_at(t * 3 + j)).collect())
.collect();
let terms: Vec<(&[Elem<Gf16>], &[u8])> = coeff_sets
.iter()
.zip(&sources)
.map(|(c, s)| (c.as_slice(), s.as_slice()))
.collect();
let mut rows = noise(row_len * nrows, 0x1000);
let mut dst = noise(row_len, 0x1100);
println!(" row {row_len} B, {nrows} rows, {nsrc} sources:");
bench(" scatter gf16b", row_len * nrows, || {
ops::mul_add_scatter::<Gf16>(
black_box(&mut rows),
row_len,
&row_coeffs,
black_box(srcs[0]),
);
});
bench(" gather gf16b", row_len * nsrc, || {
ops::mul_add_gather::<Gf16>(black_box(&mut dst), &row_coeffs[..nsrc], black_box(&srcs));
});
bench(
" matrix gf16b",
row_len * nrows * nsrc,
|| {
ops::mul_add_matrix::<Gf16>(black_box(&mut rows), row_len, nrows, &terms);
},
);
let coeff = Elem::<Gf16>::from_raw(0x53a7);
let fused = bench(" mul_into fused gf16b", row_len, || {
ops::mul_into::<Gf16>(black_box(&mut dst), coeff, black_box(srcs[0]));
});
let copied = bench(" mul_into copy+scale gf16b", row_len, || {
let dst = black_box(&mut dst);
dst.copy_from_slice(black_box(srcs[0]));
ops::mul_assign::<Gf16>(dst, coeff);
});
println!(" copy+scale/fused: {:.2}x", copied / fused);
}
println!();
}
#[derive(Clone, Copy)]
struct Rs16Tower;
impl TowerSpec for Rs16Tower {
type Base = Binary<8, Polynomial<RS>>;
const LINEAR_COEFFICIENT: u64 = 2;
const CONSTANT_COEFFICIENT: u64 = 0x80;
const NAME: &'static str = "GF(2^16)/0x11D";
}
type Gf16d = Binary<16, Tower<Rs16Tower>>;
const GF16D_COEFF_RAW: u16 = 0x53a7;
const GF16D_LENGTHS: &[usize] = &[64, 1_024, 16_384, 65_536];
fn oracle_gf8_mul(a: u8, b: u8) -> u8 {
let mut acc = 0u16;
let mut a = u16::from(a);
let mut b = u16::from(b);
loop {
if b & 1 == 1 {
acc ^= a;
}
b >>= 1;
if b == 0 {
return acc as u8;
}
a <<= 1;
if a & 0x100 != 0 {
a ^= 0x11d;
}
}
}
fn oracle_gf16d_mul(x: u16, y: u16) -> u16 {
let (x0, x1) = (x as u8, (x >> 8) as u8);
let (y0, y1) = (y as u8, (y >> 8) as u8);
let cross = oracle_gf8_mul(x1, y1);
let lo = oracle_gf8_mul(x0, y0) ^ oracle_gf8_mul(0x80, cross);
let hi = oracle_gf8_mul(x0, y1) ^ oracle_gf8_mul(x1, y0) ^ oracle_gf8_mul(2, cross);
u16::from(lo) | (u16::from(hi) << 8)
}
fn assert_gf16d_oracle() {
let mut cases = vec![
GF16D_COEFF_RAW,
0x0000,
0x0001,
0x0002,
0x00ff,
0x0100,
0x8000,
0xffff,
];
let mut state = 0x9e37_79b9_7f4a_7c15u64;
for _ in 0..8 {
state = state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1);
cases.push((state >> 48) as u16);
}
for &x in &cases {
for &y in &cases {
let got = Elem::<Gf16d>::from_raw(x)
.mul(Elem::<Gf16d>::from_raw(y))
.to_raw();
let want = oracle_gf16d_mul(x, y);
assert_eq!(
got, want,
"gf16d oracle mismatch: {x:#06x} * {y:#06x} = {got:#06x}, want {want:#06x}"
);
}
}
}
fn bench_gf16d_custom_tower() {
println!("custom scalar-routed tower gf16d — GF(2^16)/0x11D, A=2 B=0x80:");
assert_gf16d_oracle();
let coeff = Elem::<Gf16d>::from_raw(GF16D_COEFF_RAW);
let prepared = ops::Coeff::<Gf16d>::new(coeff);
for &len in GF16D_LENGTHS {
let src = noise(len, 0x3a00 + len as u64);
let mut dst = noise(len, 0x3b00 + len as u64);
println!(" row {len} B:");
bench(" mul_add one-shot gf16d", len, || {
ops::mul_add::<Gf16d>(black_box(&mut dst), coeff, black_box(&src));
});
bench(" mul_add prepared gf16d", len, || {
ops::mul_add_with::<Gf16d>(black_box(&mut dst), &prepared, black_box(&src));
});
}
bench_scalar("gf16d scalar mul", || {
let mut x = Elem::<Gf16d>::from_raw(0x1234);
for i in 0..SCALAR_ITERS {
x = black_box(x).mul(Elem::<Gf16d>::from_raw((0x53a7u16).wrapping_add(i as u16)));
}
black_box(x);
});
println!();
}
fn bench_gf2_bits() {
println!("bit-packed GF(2) — packed vs byte-per-element control:");
for &bytes in &[4 * 1024usize, 256 * 1024, 8 * 1024 * 1024, 64 * 1024 * 1024] {
let bits = 8 * bytes;
let human = if bytes >= 1024 * 1024 {
format!("{} MiB", bytes / (1024 * 1024))
} else {
format!("{} KiB", bytes / 1024)
};
let a = noise(bytes, 0x2100 + bytes as u64);
let b = noise(bytes, 0x2200 + bytes as u64);
let mut dst = noise(bytes, 0x2300 + bytes as u64);
println!(" {human} buffers ({bits} elements):");
bench(" bits::xor_assign packed", bytes, || {
fgf::bits::xor_assign(black_box(&mut dst), black_box(&a));
});
bench(" bits::and_into packed", bytes, || {
fgf::bits::and_into(black_box(&mut dst), black_box(&a), black_box(&b));
});
bench(" bits::weight packed", bytes, || {
black_box(fgf::bits::weight(black_box(&a), bits));
});
bench(" bits::dot_product packed", bytes, || {
black_box(fgf::bits::dot_product(black_box(&a), black_box(&b), bits));
});
let from = bits / 4;
let to = bits - bits / 8;
bench(" bits::xor_range 5/8 packed", bytes, || {
fgf::bits::xor_range(black_box(&mut dst), black_box(&a), bits, from, to);
});
let plan = fgf::bits::XorRange::new(bits, from, to);
bench(" bits::XorRange 5/8 packed", bytes, || {
fgf::bits::xor_range_with(black_box(&mut dst), black_box(&a), black_box(&plan));
});
bench(" ops::add_assign gf8b control", bytes, || {
ops::add_assign::<Binary<8, Polynomial<AES>>>(black_box(&mut dst), black_box(&a));
});
}
println!();
}
fn bench_gf2_short_rows() {
const ROWS: usize = 512;
const ROW_BYTES: usize = 16; let mut dst = noise(ROWS * ROW_BYTES, 0x5100);
let src = noise(ROWS * ROW_BYTES, 0x5200);
let bits = ROW_BYTES * 8;
let pairs = ROWS / 2;
println!(" {pairs} row pairs, 16-byte rows, bits 61..69:");
let one_shot = bench(" bits::xor_range 8-bit row", ROW_BYTES * pairs, || {
for i in 0..pairs {
let off = i * 2 * ROW_BYTES;
fgf::bits::xor_range(
black_box(&mut dst[off..off + ROW_BYTES]),
black_box(&src[off..off + ROW_BYTES]),
bits,
61,
69,
);
}
});
let plan = fgf::bits::XorRange::new(bits, 61, 69);
let prepared = bench(" bits::XorRange 8-bit row", ROW_BYTES * pairs, || {
for i in 0..pairs {
let off = i * 2 * ROW_BYTES;
fgf::bits::xor_range_with(
black_box(&mut dst[off..off + ROW_BYTES]),
black_box(&src[off..off + ROW_BYTES]),
black_box(&plan),
);
}
});
let per_call = |d: f64| d / pairs as f64;
println!(
" per call: one-shot {:.2} ns, prepared {:.2} ns ({:.2}x)\n",
per_call(one_shot),
per_call(prepared),
one_shot / prepared
);
}
fn bench_add_assign_rows<F: fgf::FieldKernels>(name: &str, canon: Option<fn(&mut [u8])>) {
println!("add_assign_rows — {name}:");
for &row_len in &[64usize, 1024, 64 * 1024] {
for &rows in &[1usize, 2, 4, 8, 16, 32] {
let len = row_len * rows;
let mut src = noise(len, 0x2a00 + row_len as u64);
let mut dst = noise(len, 0x2b00 + rows as u64);
if let Some(canon) = canon {
canon(&mut src);
canon(&mut dst);
}
let flat = bench(" flat add_assign", len, || {
ops::add_assign::<F>(black_box(&mut dst), black_box(&src));
});
let interleaved = bench(" add_assign_rows", len, || {
ops::add_assign_rows::<F>(black_box(&mut dst), row_len, black_box(&src));
});
let looped = bench(" add_assign per row", len, || {
let dst = black_box(&mut dst);
let src = black_box(&src);
for (d, s) in dst.chunks_exact_mut(row_len).zip(src.chunks_exact(row_len)) {
ops::add_assign::<F>(d, s);
}
});
println!(
" rows/flat: {:.2}x, rows/loop: {:.2}x ({rows} rows x {row_len} B)",
flat / interleaved,
looped / interleaved,
);
}
}
println!();
}
fn main() {
println!("fgf kernel benchmark — backend: {}", backend().name());
print_host_lines();
print_field_backends();
println!("control byte xor — drift check:");
let control_src = noise(CONTROL_BYTES, 0x57a7);
let mut control_dst = noise(CONTROL_BYTES, 0x57a8);
bench("control byte xor start", CONTROL_BYTES, || {
control_body(&mut control_dst, &control_src)
});
run_panels();
println!("control byte xor — drift check:");
bench("control byte xor end", CONTROL_BYTES, || {
control_body(&mut control_dst, &control_src)
});
}
fn run_panels() {
let args: Vec<String> = std::env::args().collect();
let has = |flag: &str| args.iter().any(|arg| arg.as_str() == flag);
let named = has("--gf") || has("--gdl") || has("--m31") || has("--gf2");
let gf = !named || has("--gf");
let gdl = !named || has("--gdl");
let m31 = !named || has("--m31");
let gf2 = !named || has("--gf2");
if has("--network-diagnostic") {
bench_network_alignment();
return;
}
#[cfg(all(feature = "simd", any(target_arch = "x86", target_arch = "x86_64")))]
if has("--blocked-diagnostic") {
bench_blocked_vs_axpy();
return;
}
println!(" (override with SIMD_BACKEND=v3_gfni_crypto|v3|v2|neon|scalar)\n");
if gf {
bench_preparation_crossover();
bench_small_row_shapes();
bench_gf16d_custom_tower();
bench_add_assign_rows::<Binary<8, Polynomial<AES>>>("gf8b", None);
bench_add_assign_rows::<Gf16>("gf16b", None);
bench_large_destination();
bench_destination_alignment();
bench_network_payloads();
}
if m31 {
bench_add_assign_rows::<Mersenne31>(
"m31 (prime control, flat default)",
Some(canonicalize_m31_lanes),
);
}
if gf2 {
bench_gf2_bits();
bench_gf2_short_rows();
}
for &len in &[4 * 1024usize, 256 * 1024, 8 * 1024 * 1024] {
if !(gf || m31 || gdl) {
continue;
}
let human = if len >= 1024 * 1024 {
format!("{} MiB", len / (1024 * 1024))
} else {
format!("{} KiB", len / 1024)
};
println!("buffer {human}:");
let mut src = noise(len, 1);
let mut dst = noise(len, 2);
let prepared16 = ops::Coeff::<Gf16>::new(Elem::<Gf16>::from_raw(0x53a7));
let mut rhs = noise(len, 0x602);
let mut product = vec![0; len];
if gf {
bench("xor gf8b", len, || {
ops::add_assign::<Binary<8, Polynomial<AES>>>(black_box(&mut dst), black_box(&src));
});
bench("mul_add gf8b", len, || {
ops::mul_add::<Binary<8, Polynomial<AES>>>(
black_box(&mut dst),
Elem::<Binary<8, Polynomial<AES>>>::from_raw(0x53),
black_box(&src),
);
});
bench("mul_add gf16b", len, || {
ops::mul_add::<Gf16>(
black_box(&mut dst),
Elem::<Gf16>::from_raw(0x53a7),
black_box(&src),
);
});
bench("mul_add prepared gf16b", len, || {
ops::mul_add_with::<Gf16>(black_box(&mut dst), &prepared16, black_box(&src));
});
bench("mul_into gf8b", len, || {
ops::mul_into::<Binary<8, Polynomial<AES>>>(
black_box(&mut product),
Elem::<Binary<8, Polynomial<AES>>>::from_raw(0x53),
black_box(&src),
);
});
bench("mul_into gf16b", len, || {
ops::mul_into::<Gf16>(
black_box(&mut product),
Elem::<Gf16>::from_raw(0x53a7),
black_box(&src),
);
});
bench("mul_assign gf8b", len, || {
ops::mul_assign::<Binary<8, Polynomial<AES>>>(
black_box(&mut dst),
Elem::<Binary<8, Polynomial<AES>>>::from_raw(0x53),
);
});
bench("elementwise gf8b", len, || {
ops::mul_elementwise::<Binary<8, Polynomial<AES>>>(
black_box(&mut product),
black_box(&src),
black_box(&rhs),
);
});
bench("elementwise gf16b", len, || {
ops::mul_elementwise::<Gf16>(
black_box(&mut product),
black_box(&src),
black_box(&rhs),
);
});
bench("elementwise_assign gf8b", len, || {
ops::mul_elementwise_assign::<Binary<8, Polynomial<AES>>>(
black_box(&mut product),
black_box(&src),
);
});
bench("elementwise_assign gf16b", len, || {
ops::mul_elementwise_assign::<Gf16>(black_box(&mut product), black_box(&src));
});
bench("mul_assign gf16b", len, || {
ops::mul_assign::<Gf16>(black_box(&mut dst), Elem::<Gf16>::from_raw(0x53a7));
});
bench("add_assign gf16b", len, || {
ops::add_assign::<Gf16>(black_box(&mut dst), black_box(&src));
});
bench("add_assign_scalar gf8b", len, || {
ops::add_assign_scalar::<Binary<8, Polynomial<AES>>>(
black_box(&mut dst),
Elem::<Binary<8, Polynomial<AES>>>::from_raw(0x53),
);
});
bench("sub_assign_scalar gf8b", len, || {
ops::sub_assign_scalar::<Binary<8, Polynomial<AES>>>(
black_box(&mut dst),
Elem::<Binary<8, Polynomial<AES>>>::from_raw(0x53),
);
});
bench("add_assign_scalar gf16b", len, || {
ops::add_assign_scalar::<Gf16>(black_box(&mut dst), Elem::<Gf16>::from_raw(0x53a7));
});
bench("sub_assign_scalar gf16b", len, || {
ops::sub_assign_scalar::<Gf16>(black_box(&mut dst), Elem::<Gf16>::from_raw(0x53a7));
});
}
if m31 {
for buf in [&mut src, &mut dst, &mut rhs, &mut product] {
canonicalize_m31_lanes(buf);
}
bench("add_assign m31", len, || {
ops::add_assign::<Mersenne31>(black_box(&mut dst), black_box(&src));
});
bench("add_assign_scalar m31", len, || {
ops::add_assign_scalar::<Mersenne31>(
black_box(&mut dst),
Elem::<Mersenne31>::from_raw(0x1234_5678),
);
});
bench("sub_assign_scalar m31", len, || {
ops::sub_assign_scalar::<Mersenne31>(
black_box(&mut dst),
Elem::<Mersenne31>::from_raw(0x1234_5678),
);
});
bench("elementwise m31", len, || {
ops::mul_elementwise::<Mersenne31>(
black_box(&mut product),
black_box(&src),
black_box(&rhs),
);
});
bench("elementwise_assign m31", len, || {
ops::mul_elementwise_assign::<Mersenne31>(black_box(&mut product), black_box(&src));
});
}
if gdl {
for buf in [&mut src, &mut dst, &mut rhs, &mut product] {
canonicalize_goldilocks_lanes(buf);
}
bench("add_assign gld", len, || {
ops::add_assign::<Goldilocks>(black_box(&mut dst), black_box(&src));
});
bench("add_assign_scalar gld", len, || {
ops::add_assign_scalar::<Goldilocks>(
black_box(&mut dst),
Elem::<Goldilocks>::from_raw(0x1234_5678_9abc_def0),
);
});
bench("sub_assign_scalar gld", len, || {
ops::sub_assign_scalar::<Goldilocks>(
black_box(&mut dst),
Elem::<Goldilocks>::from_raw(0x1234_5678_9abc_def0),
);
});
bench("elementwise gld", len, || {
ops::mul_elementwise::<Goldilocks>(
black_box(&mut product),
black_box(&src),
black_box(&rhs),
);
});
bench("elementwise_assign gld", len, || {
ops::mul_elementwise_assign::<Goldilocks>(black_box(&mut product), black_box(&src));
});
}
if m31 {
for buf in [&mut src, &mut dst, &mut rhs, &mut product] {
canonicalize_m31_lanes(buf);
}
bench("add_assign qm31", len, || {
ops::add_assign::<QuadMersenne31>(black_box(&mut dst), black_box(&src));
});
bench("add_assign_scalar qm31", len, || {
ops::add_assign_scalar::<QuadMersenne31>(
black_box(&mut dst),
Elem::<QuadMersenne31>::from_raw(0x1234_5678, 0x0987_6543),
);
});
bench("sub_assign_scalar qm31", len, || {
ops::sub_assign_scalar::<QuadMersenne31>(
black_box(&mut dst),
Elem::<QuadMersenne31>::from_raw(0x1234_5678, 0x0987_6543),
);
});
bench("elementwise qm31", len, || {
ops::mul_elementwise::<QuadMersenne31>(
black_box(&mut product),
black_box(&src),
black_box(&rhs),
);
});
bench("elementwise_assign qm31", len, || {
ops::mul_elementwise_assign::<QuadMersenne31>(
black_box(&mut product),
black_box(&src),
);
});
}
println!();
}
if !gf {
return;
}
let row_len = 64 * 1024;
for &nrows in &[2usize, 4, 8, 16] {
println!("scatter/matrix — {nrows} rows x {} KiB:", row_len / 1024);
let src = noise(row_len, 3);
let mut rows = noise(row_len * nrows, 4);
let coeffs8: Vec<_> = (0..nrows)
.map(|j| {
Elem::<Binary<8, Polynomial<AES>>>::from_raw(
(j as u8).wrapping_mul(37).wrapping_add(2),
)
})
.collect();
let coeffs16: Vec<_> = (0..nrows)
.map(|j| Elem::<Gf16>::from_raw((j as u16).wrapping_mul(9871).wrapping_add(2)))
.collect();
let scatter_coeffs8 = ops::CoeffVec::<Binary<8, Polynomial<AES>>>::new(&coeffs8);
let scatter_coeffs16 = ops::CoeffVec::<Gf16>::new(&coeffs16);
let traffic = row_len * nrows;
bench("scatter gf8b", traffic, || {
ops::mul_add_scatter::<Binary<8, Polynomial<AES>>>(
black_box(&mut rows),
row_len,
&coeffs8,
black_box(&src),
);
});
bench("scatter gf16b", traffic, || {
ops::mul_add_scatter::<Gf16>(black_box(&mut rows), row_len, &coeffs16, black_box(&src));
});
bench("scatter prepared gf8b", traffic, || {
ops::mul_add_scatter_with::<Binary<8, Polynomial<AES>>>(
black_box(&mut rows),
row_len,
scatter_coeffs8.as_ref(),
black_box(&src),
);
});
bench("scatter prepared gf16b", traffic, || {
ops::mul_add_scatter_with::<Gf16>(
black_box(&mut rows),
row_len,
scatter_coeffs16.as_ref(),
black_box(&src),
);
});
bench("scatter (unblocked) gf8b", traffic, || {
for (row, &coeff) in rows.chunks_exact_mut(row_len).zip(&coeffs8) {
ops::mul_add::<Binary<8, Polynomial<AES>>>(black_box(row), coeff, black_box(&src));
}
});
bench("scatter (unblocked) gf16b", traffic, || {
for (row, &coeff) in rows.chunks_exact_mut(row_len).zip(&coeffs16) {
ops::mul_add::<Gf16>(black_box(row), coeff, black_box(&src));
}
});
let sources: Vec<Vec<u8>> = (0..16).map(|t| noise(row_len, 100 + t as u64)).collect();
let coeff_sets: Vec<Vec<Elem<Binary<8, Polynomial<AES>>>>> = (0..8)
.map(|t| {
(0..nrows)
.map(|j| {
Elem::<Binary<8, Polynomial<AES>>>::from_raw(
((t * 31 + j * 17) as u8).wrapping_add(1),
)
})
.collect()
})
.collect();
let coeff_sets16: Vec<Vec<Elem<Gf16>>> = (0..8)
.map(|t| {
(0..nrows)
.map(|j| Elem::<Gf16>::from_raw(((t * 7919 + j * 613) as u16).wrapping_add(1)))
.collect()
})
.collect();
#[allow(clippy::type_complexity)]
let terms: Vec<(&[Elem<Binary<8, Polynomial<AES>>>], &[u8])> = coeff_sets
.iter()
.zip(&sources)
.map(|(c, s)| (c.as_slice(), s.as_slice()))
.collect();
let terms16: Vec<(&[Elem<Gf16>], &[u8])> = coeff_sets16
.iter()
.zip(&sources)
.map(|(c, s)| (c.as_slice(), s.as_slice()))
.collect();
let matrix_coeffs8: Vec<_> = coeff_sets.iter().flatten().copied().collect();
let matrix_coeffs16: Vec<_> = coeff_sets16.iter().flatten().copied().collect();
let matrix8 = ops::CoeffMatrix::<Binary<8, Polynomial<AES>>>::from_source_major(
8,
nrows,
&matrix_coeffs8,
);
let matrix16 = ops::CoeffMatrix::<Gf16>::from_source_major(8, nrows, &matrix_coeffs16);
let matrix_srcs: Vec<&[u8]> = sources.iter().take(8).map(Vec::as_slice).collect();
let traffic = row_len * nrows * 8;
bench("matrix (selected) gf8b", traffic, || {
ops::mul_add_matrix::<Binary<8, Polynomial<AES>>>(
black_box(&mut rows),
row_len,
nrows,
&terms,
);
});
bench("matrix (unblocked AXPY) gf8b", traffic, || {
for &(coeffs, src) in &terms {
for (row, &coeff) in rows.chunks_exact_mut(row_len).zip(coeffs) {
ops::mul_add::<Binary<8, Polynomial<AES>>>(black_box(row), coeff, src);
}
}
});
bench("matrix (selected) gf16b", traffic, || {
ops::mul_add_matrix::<Gf16>(black_box(&mut rows), row_len, nrows, &terms16);
});
bench("matrix prepared gf8b", traffic, || {
ops::mul_add_matrix_with::<Binary<8, Polynomial<AES>>>(
black_box(&mut rows),
row_len,
&matrix8,
black_box(&matrix_srcs),
);
});
bench("matrix prepared gf16b", traffic, || {
ops::mul_add_matrix_with::<Gf16>(
black_box(&mut rows),
row_len,
&matrix16,
black_box(&matrix_srcs),
);
});
bench("matrix (unblocked AXPY) gf16b", traffic, || {
for &(coeffs, src) in &terms16 {
for (row, &coeff) in rows.chunks_exact_mut(row_len).zip(coeffs) {
ops::mul_add::<Gf16>(black_box(row), coeff, src);
}
}
});
let gather_srcs: Vec<&[u8]> = sources.iter().take(nrows).map(Vec::as_slice).collect();
let mut gathered = noise(row_len, 5);
let gather_coeffs8 = ops::CoeffVec::<Binary<8, Polynomial<AES>>>::new(&coeffs8);
let gather_coeffs16 = ops::CoeffVec::<Gf16>::new(&coeffs16);
let gather_traffic = row_len * nrows;
bench("gather (selected) gf8b", gather_traffic, || {
ops::mul_add_gather::<Binary<8, Polynomial<AES>>>(
black_box(&mut gathered),
&coeffs8,
black_box(&gather_srcs),
);
});
bench("gather (unblocked) gf8b", gather_traffic, || {
for (&coeff, &source) in coeffs8.iter().zip(&gather_srcs) {
ops::mul_add::<Binary<8, Polynomial<AES>>>(
black_box(&mut gathered),
coeff,
black_box(source),
);
}
});
bench("gather (selected) gf16b", gather_traffic, || {
ops::mul_add_gather::<Gf16>(
black_box(&mut gathered),
&coeffs16,
black_box(&gather_srcs),
);
});
bench("gather prepared gf8b", gather_traffic, || {
ops::mul_add_gather_with::<Binary<8, Polynomial<AES>>>(
black_box(&mut gathered),
gather_coeffs8.as_ref(),
black_box(&gather_srcs),
);
});
bench("gather prepared gf16b", gather_traffic, || {
ops::mul_add_gather_with::<Gf16>(
black_box(&mut gathered),
gather_coeffs16.as_ref(),
black_box(&gather_srcs),
);
});
bench("gather (unblocked) gf16b", gather_traffic, || {
for (&coeff, &source) in coeffs16.iter().zip(&gather_srcs) {
ops::mul_add::<Gf16>(black_box(&mut gathered), coeff, black_box(source));
}
});
println!();
}
let tier3_len = 256 * 1024;
let tier3_src = noise(tier3_len, 0x900);
let mut tier3_dst = noise(tier3_len, 0x901);
println!("Tier 3 field cost — {tier3_len} bytes:");
bench("mul_add polynomial tower gf16b", tier3_len, || {
ops::mul_add::<Gf16>(
black_box(&mut tier3_dst),
Elem::<Gf16>::from_raw(0x53a7),
black_box(&tier3_src),
);
});
bench("mul_add polynomial tower gf32b", tier3_len, || {
ops::mul_add::<Gf32>(
black_box(&mut tier3_dst),
Elem::<Gf32>::from_raw(0xdead_beef),
black_box(&tier3_src),
);
});
bench("mul_add polynomial tower gf64b", tier3_len, || {
ops::mul_add::<Gf64>(
black_box(&mut tier3_dst),
Elem::<Gf64>::from_raw(0x0123_4567_89ab_cdef),
black_box(&tier3_src),
);
});
bench("mul_add canonical Fan-Paar fp16", tier3_len, || {
ops::mul_add::<FanPaar16Field>(
black_box(&mut tier3_dst),
Elem::<FanPaar16Field>::from_raw(0xe2de),
black_box(&tier3_src),
);
});
bench("mul_add canonical Fan-Paar fp32", tier3_len, || {
ops::mul_add::<FanPaar32Field>(
black_box(&mut tier3_dst),
Elem::<FanPaar32Field>::from_raw(0x03e2_1cea),
black_box(&tier3_src),
);
});
bench("mul_add canonical Fan-Paar fp64", tier3_len, || {
ops::mul_add::<FanPaar64Field>(
black_box(&mut tier3_dst),
Elem::<FanPaar64Field>::from_raw(0x070f_870d_cd9c_1d88),
black_box(&tier3_src),
);
});
}