#![allow(clippy::cast_precision_loss)]
use std::hint::black_box;
use std::time::Instant;
use fsqlite_parser::{
Lexer, StatementParseScratch, parse_single_statement_with_scratch,
set_force_interner_scan_bench,
};
fn median(mut v: Vec<f64>) -> f64 {
v.sort_by(|a, b| a.partial_cmp(b).unwrap());
v[v.len() / 2]
}
#[test]
#[ignore = "A/B bench; run under --profile release-perf"]
fn interner_retained_bytes_incremental_ab() {
let mut cols = String::new();
for i in 0..240 {
if i > 0 {
cols.push_str(", ");
}
cols.push_str(&format!("c{i}"));
}
let warm_sql = format!("SELECT {cols} FROM warm_table");
let measure_sql = "SELECT id, v FROM t WHERE id = 5";
let mut scratch = StatementParseScratch::default();
let _ = parse_single_statement_with_scratch(&warm_sql, &mut scratch);
for _ in 0..100 {
let _ = parse_single_statement_with_scratch(measure_sql, &mut scratch);
}
let samples = 40usize;
let k = 5000usize;
let run = |force_scan: bool, scratch: &mut StatementParseScratch| -> f64 {
set_force_interner_scan_bench(force_scan);
let t = Instant::now();
for _ in 0..k {
let _ = black_box(parse_single_statement_with_scratch(
black_box(measure_sql),
scratch,
));
}
t.elapsed().as_nanos() as f64 / k as f64
};
let mut scan_ns = Vec::new();
let mut incr_ns = Vec::new();
let mut null_ns = Vec::new();
for _ in 0..samples {
scan_ns.push(run(true, &mut scratch));
incr_ns.push(run(false, &mut scratch));
null_ns.push(run(false, &mut scratch));
}
set_force_interner_scan_bench(false);
let ms = median(scan_ns);
let mi = median(incr_ns);
let mn = median(null_ns);
eprintln!(
"\n########## bd-5310l interner retained_bytes O(N)-fold vs O(1)-incremental A/B ##########"
);
eprintln!(
" ~240-entry warm interner, {samples} samples x {k} parses/arm:\n \
SCAN arm (O(entries) fold) parse median = {ms:8.1} ns/stmt\n \
INCR arm (O(1) running sum) parse median = {mi:8.1} ns/stmt\n \
speedup (scan/incr) = {:.3}x\n \
null control (incr vs incr) = {:.3}x [{mn:.1} vs {mi:.1}]\n \
per-parse fold cost eliminated = {:.1} ns",
ms / mi,
mn / mi,
ms - mi,
);
eprintln!("########## end interner retained_bytes A/B ##########\n");
}
#[test]
#[ignore = "profile; run under --profile release-perf"]
fn parse_phase_lex_vs_parse_split() {
let sqls = [
"SELECT 1",
"SELECT id, v FROM t WHERE id = 5",
"SELECT id, v, k FROM t WHERE k BETWEEN 10 AND 20 ORDER BY k, id",
"INSERT INTO t (a, b, c) VALUES (1, 'hello world', 3.5)",
"SELECT a.id, b.name FROM users a JOIN accounts b ON a.id = b.uid WHERE a.age > 21",
];
let n = 300_000u32;
eprintln!("\n########## bd-5310l parse-phase lex-vs-parse split ##########");
for sql in sqls {
for _ in 0..1000 {
black_box(Lexer::tokenize(black_box(sql)));
}
let t = Instant::now();
for _ in 0..n {
black_box(Lexer::tokenize(black_box(sql)));
}
let lex = t.elapsed().as_nanos() as f64 / f64::from(n);
let mut scratch = StatementParseScratch::default();
for _ in 0..1000 {
let _ = black_box(parse_single_statement_with_scratch(
black_box(sql),
&mut scratch,
));
}
let t = Instant::now();
for _ in 0..n {
let _ = black_box(parse_single_statement_with_scratch(
black_box(sql),
&mut scratch,
));
}
let full = t.elapsed().as_nanos() as f64 / f64::from(n);
eprintln!(
"[{sql}]\n lex = {lex:7.1} ns\n full parse = {full:7.1} ns\n parse-only = {:7.1} ns ({:.0}% lex / {:.0}% parse)",
full - lex,
lex / full * 100.0,
(full - lex) / full * 100.0,
);
}
eprintln!("########## end parse-phase split ##########\n");
}