First commit. Version 0.1.0
This commit is contained in:
150
benches/lookup.rs
Normal file
150
benches/lookup.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
//! Per-frame dispatch hot path benches.
|
||||
//!
|
||||
//! The renderer rebuilds the widget tree on every frame, then hit-tests
|
||||
//! pointer events against the resulting flat `Vec<LaidOutWidget>` and
|
||||
//! looks up handlers by `flat_idx`. Both calls are O(N) in the slice
|
||||
//! length. Run with `cargo bench --bench lookup`.
|
||||
|
||||
use criterion::{ black_box, criterion_group, criterion_main, BenchmarkId, Criterion };
|
||||
|
||||
use ltk::test_support::{ find_handlers, find_widget, find_widget_at, LaidOutWidget, WidgetHandlers };
|
||||
use ltk::{ Point, Rect };
|
||||
|
||||
// ── Fixture builders ──────────────────────────────────────────────────────────
|
||||
|
||||
/// Build N widgets laid out in a vertical strip. Each widget is 100×30 with a
|
||||
/// 1px gap, so `find_widget_at(x=50, y=k*31+15)` lands inside widget `k`. Half
|
||||
/// of them carry a Button handler so `find_handlers` exercises the
|
||||
/// match-and-clone path on roughly every other lookup.
|
||||
fn build_widgets( n: usize ) -> Vec<LaidOutWidget<()>>
|
||||
{
|
||||
( 0 .. n ).map( |i|
|
||||
{
|
||||
let rect = Rect
|
||||
{
|
||||
x: 0.0,
|
||||
y: ( i as f32 ) * 31.0,
|
||||
width: 100.0,
|
||||
height: 30.0,
|
||||
};
|
||||
let handlers = if i % 2 == 0
|
||||
{
|
||||
WidgetHandlers::Button { on_press: Some( () ), on_long_press: None, on_drag_start: None, on_escape: None, repeating: false }
|
||||
} else {
|
||||
WidgetHandlers::None
|
||||
};
|
||||
LaidOutWidget
|
||||
{
|
||||
rect,
|
||||
flat_idx: i,
|
||||
id: None,
|
||||
paint_rect: rect,
|
||||
handlers,
|
||||
keyboard_focusable: true,
|
||||
}
|
||||
} ).collect()
|
||||
}
|
||||
|
||||
// ── Benches ───────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Pointer hit testing on a hot path. Three sub-benches:
|
||||
/// - last: hit the last widget (worst case under reverse iteration is the
|
||||
/// *first* widget; the helper iterates from the end to honour
|
||||
/// "topmost wins", so the last widget in slice order is the cheap
|
||||
/// one and the first is the expensive one).
|
||||
/// - first: hit the first widget — full-slice walk.
|
||||
/// - miss: point that lies outside every rect — also a full-slice walk
|
||||
/// but never returns early.
|
||||
fn bench_find_widget_at( c: &mut Criterion )
|
||||
{
|
||||
let mut group = c.benchmark_group( "find_widget_at" );
|
||||
|
||||
for &n in &[ 10usize, 100, 1000 ]
|
||||
{
|
||||
let widgets = build_widgets( n );
|
||||
|
||||
// Hit on the last widget in slice order — `find_widget_at` walks in
|
||||
// reverse, so this returns on the first iteration.
|
||||
let last_y = ( n as f32 - 1.0 ) * 31.0 + 15.0;
|
||||
group.bench_with_input( BenchmarkId::new( "hit_last", n ), &n, |b, _|
|
||||
{
|
||||
b.iter( ||
|
||||
{
|
||||
let p = Point { x: 50.0, y: last_y };
|
||||
black_box( find_widget_at( black_box( &widgets ), black_box( p ) ) )
|
||||
} );
|
||||
} );
|
||||
|
||||
// Hit on the first widget — full reverse walk.
|
||||
group.bench_with_input( BenchmarkId::new( "hit_first", n ), &n, |b, _|
|
||||
{
|
||||
b.iter( ||
|
||||
{
|
||||
let p = Point { x: 50.0, y: 15.0 };
|
||||
black_box( find_widget_at( black_box( &widgets ), black_box( p ) ) )
|
||||
} );
|
||||
} );
|
||||
|
||||
// Miss — point sits to the right of every rect.
|
||||
group.bench_with_input( BenchmarkId::new( "miss", n ), &n, |b, _|
|
||||
{
|
||||
b.iter( ||
|
||||
{
|
||||
let p = Point { x: 9_999.0, y: 9_999.0 };
|
||||
black_box( find_widget_at( black_box( &widgets ), black_box( p ) ) )
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
/// `flat_idx` lookup. Linear scan; we measure the worst case (last entry)
|
||||
/// since it's what bounds latency on real frames.
|
||||
fn bench_find_widget( c: &mut Criterion )
|
||||
{
|
||||
let mut group = c.benchmark_group( "find_widget" );
|
||||
|
||||
for &n in &[ 10usize, 100, 1000 ]
|
||||
{
|
||||
let widgets = build_widgets( n );
|
||||
let target = n - 1;
|
||||
|
||||
group.bench_with_input( BenchmarkId::new( "last", n ), &n, |b, _|
|
||||
{
|
||||
b.iter( ||
|
||||
{
|
||||
black_box( find_widget( black_box( &widgets ), black_box( target ) ) )
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
/// Same shape as `find_widget` but exercises the handler-cloning path on hit.
|
||||
/// Useful as a separate measurement because the clone cost grows with the
|
||||
/// handler payload, even though the scan itself does not.
|
||||
fn bench_find_handlers( c: &mut Criterion )
|
||||
{
|
||||
let mut group = c.benchmark_group( "find_handlers" );
|
||||
|
||||
for &n in &[ 10usize, 100, 1000 ]
|
||||
{
|
||||
let widgets = build_widgets( n );
|
||||
let target = n - 1;
|
||||
|
||||
group.bench_with_input( BenchmarkId::new( "last", n ), &n, |b, _|
|
||||
{
|
||||
b.iter( ||
|
||||
{
|
||||
black_box( find_handlers( black_box( &widgets ), black_box( target ) ) )
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!( benches, bench_find_widget_at, bench_find_widget, bench_find_handlers );
|
||||
criterion_main!( benches );
|
||||
Reference in New Issue
Block a user