refactor(selection): migrate Scene.selection to Arc<RefCell> to avoid per-frame deep clone

Scene.selection was Rc<RefCell<SelectionState>> (31 fields, ~1.6KB
with poly_points) cloned deep per view_main per pane via
(*sel_ref).clone() at view/mod.rs:760. Replace with
Arc<RefCell<SelectionState>> (Arc::new(RefCell::new(...)) at
scene/mod.rs:1436,1868), SelectionCanvas holds Arc<RefCell> and
selection_overlay takes Arc<RefCell> (overlay.rs:365,415),
view_main does Arc::clone(&tab.scene.selection) (atomic bump)
instead of deep clone. sel_ref stays borrowed for field reads
(vp_size etc.).

Bench (release, n=5000): SelectionState::clone 39-68ns (64pts) /
11-22ns (0pts) vs Arc::clone 7ns — 5-9x on the hot path.

Tests:
- selection_is_arc_not_refcell: asserts type_name contains Arc
- bench_selection_state_clone / bench_selection_arc_clone / bench_selection_state_clone_empty (ignored, release)
This commit is contained in:
Karim Jerbi committed 2026-09-02 22:14:32 +01:00
1 parent d2338fa10f
commit 46190ccba1
4 files changed
+129 -29

No files matched your search

+83
View File
@@ -77,3 +77,86 @@ impl SelectionState {
self.poly_crossing = false;
}
}
#[cfg(test)]
mod bench_selection_clone_tests {
use super::SelectionState;
use iced::Point;
use std::hint::black_box;
use std::sync::Arc;
use std::time::Instant;
fn make_state(poly_len: usize) -> SelectionState {
let mut s = SelectionState {
vp_size: (1920.0, 1080.0),
poly_points: vec![Point::new(10.0, 10.0); poly_len],
..Default::default()
};
s.box_anchor = Some(Point::new(0.0, 0.0));
s.box_current = Some(Point::new(100.0, 100.0));
s
}
#[test]
#[ignore]
fn bench_selection_state_clone() {
let state = make_state(64);
for _ in 0..20 {
black_box(state.clone());
}
let n = 5000u32;
let start = Instant::now();
for _ in 0..n {
black_box(state.clone());
}
let elapsed = start.elapsed();
let per = elapsed / n;
println!(
"SelectionState::clone (deep, 64 pts): {:?} per clone (n={}, total {:?})",
per, n, elapsed
);
assert!(per.as_secs_f64() > 0.0);
}
#[test]
#[ignore]
fn bench_selection_arc_clone() {
let state = Arc::new(make_state(64));
for _ in 0..20 {
black_box(Arc::clone(&state));
}
let n = 5000u32;
let start = Instant::now();
for _ in 0..n {
black_box(Arc::clone(&state));
}
let elapsed = start.elapsed();
let per = elapsed / n;
println!(
"Arc<SelectionState>::clone (Arc bump, 64 pts): {:?} per clone (n={}, total {:?})",
per, n, elapsed
);
assert!(per.as_secs_f64() > 0.0);
}
#[test]
#[ignore]
fn bench_selection_state_clone_empty() {
let state = make_state(0);
for _ in 0..20 {
black_box(state.clone());
}
let n = 5000u32;
let start = Instant::now();
for _ in 0..n {
black_box(state.clone());
}
let elapsed = start.elapsed();
let per = elapsed / n;
println!(
"SelectionState::clone (deep, 0 pts): {:?} per clone (n={}, total {:?})",
per, n, elapsed
);
assert!(per.as_secs_f64() > 0.0);
}
}