use std::ops::Range;
use super::op_topn::op_topn;
use super::plan::TopNPlan;
use super::tests::Harness;
use crate::repr::{Batch, BatchBuilder};
use crate::schema::{SchemaColumn, SchemaDescriptor, TypeCode};
use crate::test_support::{mix, TestTrace};
use gnitz_foundation::perf::Counter;
use gnitz_wire::OrderKey;
const N: u64 = 1 << 16;
struct Epochs {
h: Harness,
ran: usize,
}
impl Epochs {
fn new(plan: TopNPlan) -> Self {
Epochs { h: Harness::new(plan, &[]), ran: 0 }
}
fn run(&mut self, counter: &Counter, delta: &Batch) -> u64 {
let Harness { plan, index, .. } = &mut self.h;
let (epoch, instructions) =
counter.measure(|| op_topn(delta, &mut |first, last| index.cursor_within(first, last), plan));
index.ingest(epoch.index_entries);
self.ran += 1;
if self.ran.is_multiple_of(8) {
let all = index.cursor().materialize();
*index = TestTrace::new(plan.index.schema);
index.ingest((*all).clone());
}
instructions
}
fn bulk(&mut self, counter: &Counter, first: &Batch, second: &Batch) -> [f64; 3] {
let retraction = first.clone().negated().into_consolidated();
[first, second, &retraction].map(|d| self.run(counter, d) as f64 / d.count as f64)
}
}
#[test]
#[ignore = "benchmark; run with --release --ignored --nocapture --test-threads=1"]
fn op_topn_bench() {
let counter = Counter::instructions();
let asc = |col| OrderKey { col, desc: false, nulls_first: false };
type Put = fn(&mut BatchBuilder, u64);
let i64_key = SchemaColumn::new(TypeCode::I64, false);
let group_i64 = SchemaColumn::new(TypeCode::I64, false);
let group_null = SchemaColumn::new(TypeCode::I64, true);
let cases: [(&str, &[SchemaColumn], SchemaColumn, Put, u64); 7] = [
("global, I64 key", &[], i64_key, |b, i| b.put_u64(mix(i)), 1),
(
"I64 group, I64 key",
&[group_i64],
i64_key,
|b, i| b.put_u64(mix(i)),
4096,
),
(
"I64 group, dense I64 key",
&[group_i64],
i64_key,
|b, i| b.put_u64(i % 256),
4096,
),
(
"I64 group, nullable I64 key",
&[group_i64],
SchemaColumn::new(TypeCode::I64, true),
|b, i| b.put_opt_int((i % 4 != 0).then_some(mix(i) as u128)),
4096,
),
(
"I64 group, STRING key",
&[group_i64],
SchemaColumn::new(TypeCode::String, false),
|b, i| b.put_string(&format!("{:040}", mix(i))),
4096,
),
(
"nullable I64 group, I64 key",
&[group_null],
i64_key,
|b, i| b.put_u64(mix(i)),
4096,
),
(
"2 nullable I64 groups, I64 key",
&[group_null; 2],
i64_key,
|b, i| b.put_u64(mix(i)),
4096,
),
];
for (label, group, key, put, groups) in cases {
let mut cols = vec![SchemaColumn::new(TypeCode::U64, false)];
cols.extend_from_slice(group);
cols.push(key);
let schema = SchemaDescriptor::new(&cols, &[0]);
let group_cols: Vec<u32> = (1..=group.len() as u32).collect();
let make = |salt: u64| {
let mut b = BatchBuilder::new(&schema);
for i in 0..N {
b.begin_row((i + salt * N) as u128, 1);
for _ in group {
b.put_int((mix(i) % groups) as u128);
}
put(&mut b, i + salt * N);
b.end_row();
}
b.finish().into_consolidated()
};
let plan = TopNPlan::from_wire(&schema, &group_cols, &[asc(cols.len() as u16 - 1)], 10, 0).unwrap();
let mut e = Epochs::new(plan);
let (d1, d2) = (make(1), make(2));
let index = &e.h.plan.index;
let bytes = index.batch(&d1, e.h.plan.key.carried()).total_bytes() as f64 / N as f64;
let pk = index.schema.pk_stride();
let [e1, e2, e3] = e.bulk(&counter, &d1, &d2);
println!(
"op_topn_bench {label:<31} index {bytes:5.1} B/row (pk {pk:2}), empty {e1:6.1}, populated {e2:6.1}, \
retraction {e3:6.1} instr/row"
);
}
}
fn window_schema() -> SchemaDescriptor {
let i64_col = SchemaColumn::new(TypeCode::I64, false);
SchemaDescriptor::new(&[SchemaColumn::new(TypeCode::U64, false), i64_col, i64_col], &[0])
}
fn window_rows(ids: Range<u64>, group: impl Fn(u64) -> u64, key: impl Fn(u64) -> u64, w: i64) -> Batch {
let mut b = BatchBuilder::new(&window_schema());
for i in ids {
b.begin_row(i as u128, w);
b.put_int(group(i) as u128);
b.put_u64(key(i));
b.end_row();
}
b.finish().into_consolidated()
}
fn by_key(desc: bool) -> [OrderKey; 1] {
[OrderKey { col: 2, desc, nulls_first: false }]
}
#[test]
#[ignore = "benchmark; run with --release --ignored --nocapture --test-threads=1"]
fn op_topn_window_bench() {
const ONES: u64 = 256;
let counter = Counter::instructions();
let cases: [(u64, u64, u64); 8] = [
(4096, 10, 0),
(4096, 1000, 0),
(16, 10, 0),
(16, 1000, 0),
(16, 10, 100),
(16, 1000, 1000),
(1, 10, 0),
(1, u64::MAX, 1),
];
for (groups, limit, offset) in cases {
let group = |i: u64| mix(i) % groups;
let bulk = |i: u64| (mix(i) >> 2) | (1 << 62);
let plan = TopNPlan::from_wire(&window_schema(), &[1], &by_key(false), limit, offset).unwrap();
let mut e = Epochs::new(plan);
let [e1, e2, e3] = e.bulk(
&counter,
&window_rows(0..N, group, bulk, 1),
&window_rows(N..2 * N, group, bulk, 1),
);
let mut ones = |key: &dyn Fn(u64) -> u64, base: u64, w: i64| {
let total: u64 = (base..base + ONES)
.map(|i| e.run(&counter, &window_rows(i..i + 1, group, key, w)))
.sum();
total as f64 / ONES as f64
};
let below = ones(&|i| i64::MAX as u64 - i, 3 * N, 1);
let head = ones(&|i| 5 * N - i, 4 * N, 1);
let unhead = ones(&|i| 5 * N - i, 4 * N, -1);
println!(
"op_topn_window_bench groups {groups:4} limit {limit:20} offset {offset:4}: empty {e1:7.1}, \
populated {e2:7.1}, retraction {e3:7.1} instr/row; one row below {below:9.0}, at head {head:9.0}, \
head retracted {unhead:9.0} instr/epoch"
);
}
}
#[test]
#[ignore = "benchmark; run with --release --ignored --nocapture --test-threads=1"]
fn op_topn_window_ordered_bench() {
let counter = Counter::instructions();
for (groups, limit) in [(1u64, 10u64), (1, 100_000), (16, 10)] {
let group = |i: u64| i * groups / (4 * N);
let plan = TopNPlan::from_wire(&window_schema(), &[1], &by_key(false), limit, 0).unwrap();
let [e1, e2, e3] = Epochs::new(plan).bulk(
&counter,
&window_rows(0..N, group, |i| i, 1),
&window_rows(N..2 * N, group, |i| i, 1),
);
println!(
"op_topn_window_ordered_bench groups {groups:2} limit {limit:6}: empty {e1:7.1}, \
populated {e2:7.1}, retraction {e3:7.1} instr/row"
);
}
}
#[test]
#[ignore = "benchmark; run with --release --ignored --nocapture --test-threads=1"]
fn op_topn_window_descending_bench() {
let counter = Counter::instructions();
for limit in [10u64, 1000] {
let plan = TopNPlan::partial(&window_schema(), &by_key(true), limit, 0).unwrap();
let [e1, e2, e3] = Epochs::new(plan).bulk(
&counter,
&window_rows(0..N, |_| 0, |i| i, 1),
&window_rows(N..2 * N, |_| 0, |i| i, 1),
);
println!(
"op_topn_window_descending_bench limit {limit:4}: empty {e1:7.1}, \
populated {e2:7.1}, retraction {e3:7.1} instr/row"
);
}
}