Skip to main content

bench_iloc_gather/
bench_iloc_gather.rs

1//! Bench + golden digest for Series::iloc — typed gather lever.
2//!
3//! Run: cargo run -p fp-frame --example bench_iloc_gather --release
4//!
5//! iloc is a pure positional gather (no sort). It cloned a 32 B Scalar per row
6//! and rebuilt the column via Column::from_values; routing through the typed
7//! Column::take_positions keeps an all-valid Int64/Float64 buffer contiguous.
8//! Output (values, dtype, negative-index handling) is bit-identical.
9
10use std::time::Instant;
11
12use fp_frame::Series;
13use fp_index::IndexLabel;
14use fp_types::{NullKind, Scalar};
15
16fn s_i64(vals: Vec<i64>) -> Series {
17    let idx: Vec<IndexLabel> = (0..vals.len() as i64).map(IndexLabel::Int64).collect();
18    Series::from_values("s", idx, vals.into_iter().map(Scalar::Int64).collect()).unwrap()
19}
20
21fn s_scalars(vals: Vec<Scalar>) -> Series {
22    let idx: Vec<IndexLabel> = (0..vals.len() as i64).map(IndexLabel::Int64).collect();
23    Series::from_values("s", idx, vals).unwrap()
24}
25
26fn golden() -> String {
27    let mut out = String::new();
28    let s = s_i64(vec![10, 20, 30, 40, 50]);
29    // reorder + negative indices + duplicates
30    let r = s.iloc(&[4, 0, -1, 2, -5, 2]).unwrap();
31    out.push_str(&format!("i64_lbls={:?}\n", r.index().labels()));
32    out.push_str(&format!("i64_vals={:?}\n", r.values()));
33
34    // Float64 incl NaN
35    let f = s_scalars(vec![
36        Scalar::Float64(1.5),
37        Scalar::Float64(f64::NAN),
38        Scalar::Float64(-3.0),
39    ]);
40    out.push_str(&format!("f64={:?}\n", f.iloc(&[2, 1, 0]).unwrap().values()));
41
42    // Nullable Int64 (Null present => not all-valid path)
43    let ni = s_scalars(vec![
44        Scalar::Int64(7),
45        Scalar::Null(NullKind::NaN),
46        Scalar::Int64(9),
47    ]);
48    out.push_str(&format!("ni={:?}\n", ni.iloc(&[1, 2, 0]).unwrap().values()));
49
50    // Utf8 + Bool
51    let u = s_scalars(
52        vec!["a", "b", "c"]
53            .into_iter()
54            .map(|x| Scalar::Utf8(x.into()))
55            .collect(),
56    );
57    out.push_str(&format!("utf8={:?}\n", u.iloc(&[2, -3]).unwrap().values()));
58    let b = s_scalars(vec![Scalar::Bool(true), Scalar::Bool(false)]);
59    out.push_str(&format!(
60        "bool={:?}\n",
61        b.iloc(&[1, 0, 1]).unwrap().values()
62    ));
63
64    // Out-of-bounds errors
65    out.push_str(&format!("oob_err={}\n", s.iloc(&[99]).is_err()));
66    out
67}
68
69fn main() {
70    let g = golden();
71    print!("GOLDEN_BEGIN\n{g}GOLDEN_END\n");
72
73    let n: usize = 1_000_000;
74    let s = s_i64((0..n as i64).map(|v| v * 2).collect());
75    // Shuffled full permutation (LCG).
76    let mut x: u64 = 0xdead_beef;
77    let mut pos: Vec<i64> = (0..n as i64).collect();
78    for i in (1..n).rev() {
79        x = x
80            .wrapping_mul(6364136223846793005)
81            .wrapping_add(1442695040888963407);
82        let j = (x >> 16) as usize % (i + 1);
83        pos.swap(i, j);
84    }
85
86    let _ = s.iloc(&pos).unwrap(); // warmup
87
88    let t = Instant::now();
89    let r = s.iloc(&pos).unwrap();
90    let d = t.elapsed();
91    assert_eq!(r.len(), n);
92
93    println!("TIMING n={n} iloc={:.3}ms", d.as_secs_f64() * 1e3);
94}