facett-graph3d/examples/aos_crossover.rs
//! **Where does the parked pool actually beat one core — over the REAL node array?**
//!
//! `split_probe` fed both lanes a packed `Vec<[f32;3]>`. The crate does not have one: it
//! projects out of `&[Node3]`, which is `{ String, Color32, [f32;3] }` — so every 12
//! useful bytes drag a ~40-byte struct, and a 64-byte cache line carries barely one
//! point. Setting `PROJ_PARALLEL_MIN` from the packed number was measuring a layout the
//! shipped code does not use.
//!
//! Run: cargo run --release -p facett-graph3d --example aos_crossover
use std::time::Instant;
use egui::Pos2;
use facett_graph3d::{dataset, Graph3D, Layout3D};
const FRAMES: usize = 60;
#[inline]
fn project_pt(p: [f32; 3], center: Pos2, scale: f32, s: f32, c: f32) -> (Pos2, f32) {
let x = p[0] * c + p[2] * s;
let z = -p[0] * s + p[2] * c;
(Pos2::new(center.x + x * scale, center.y - p[1] * scale), z)
}
fn main() {
let pool = znippy_zoomies::gatling_map::Pool::new(0);
println!("# AoS crossover — serial vs parked pool over the REAL &[Node3]");
println!(
"machine = {} threads, workers = {}",
std::thread::available_parallelism().map(|n| n.get()).unwrap_or(0),
pool.workers()
);
println!();
println!("{:>9} {:>13} {:>13} {:>9} {:>13} {:>9}", "n", "serial AoS", "pooled AoS", "speedup", "pooled SoA", "SoA gain");
let (w, h) = (1280.0f32, 800.0f32);
let center = Pos2::new(w * 0.5, h * 0.5);
let scale = w.min(h) * 0.40;
for &want in &[30_000usize, 100_000, 300_000, 1_000_000] {
let d = dataset::generate(&dataset::GraphSpec::sized(want));
let g = Graph3D::new(d.nodes, d.edges, Layout3D::Sphere);
let n = g.nodes.len();
let soa: Vec<[f32; 3]> = g.nodes.iter().map(|nd| nd.pos).collect();
let yaws: Vec<f32> = (0..FRAMES).map(|f| f as f32 * 0.05).collect();
let per = |ms: f64| ms / FRAMES as f64;
let mut out = vec![(Pos2::ZERO, 0.0f32); n];
let t = Instant::now();
for &yaw in &yaws {
let (s, c) = yaw.sin_cos();
for (o, nd) in out.iter_mut().zip(g.nodes.iter()) {
*o = project_pt(nd.pos, center, scale, s, c);
}
std::hint::black_box(&out);
}
let ser = per(t.elapsed().as_secs_f64() * 1000.0);
let t = Instant::now();
for &yaw in &yaws {
let (s, c) = yaw.sin_cos();
pool.map_slice(&g.nodes, &mut out, |nd| project_pt(nd.pos, center, scale, s, c));
std::hint::black_box(&out);
}
let par_aos = per(t.elapsed().as_secs_f64() * 1000.0);
let t = Instant::now();
for &yaw in &yaws {
let (s, c) = yaw.sin_cos();
pool.map_slice(&soa, &mut out, |p| project_pt(*p, center, scale, s, c));
std::hint::black_box(&out);
}
let par_soa = per(t.elapsed().as_secs_f64() * 1000.0);
println!(
"{:>9} {:>10.3} ms {:>10.3} ms {:>8.2}x {:>10.3} ms {:>8.2}x",
n, ser, par_aos, ser / par_aos.max(1e-9), par_soa, par_aos / par_soa.max(1e-9)
);
}
println!();
println!("`speedup` > 1.00x means the pool beat one core AT THAT LAYOUT. `SoA gain` is what");
println!("packing the positions alone buys the SAME pooled lane — the cost of striding Node3.");
}