Skip to content

Commit 9ee4561

Browse files
committed
improve 05_swap benchmark
1 parent 3dbc932 commit 9ee4561

1 file changed

Lines changed: 75 additions & 14 deletions

File tree

packages/yew/src/dom_bundle/blist.rs

Lines changed: 75 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::dom_bundle::{DomBundle, Reconcilable};
44
use crate::html::{AnyScope, NodeRef};
55
use crate::virtual_dom::{Key, VList, VNode, VText};
66
use std::borrow::Borrow;
7+
use std::cmp::Ordering;
78
use std::collections::HashSet;
89
use std::hash::Hash;
910
use std::ops::Deref;
@@ -28,6 +29,7 @@ impl Deref for BList {
2829
}
2930

3031
/// Helper struct, that keeps the position where the next element is to be placed at
32+
#[derive(Clone)]
3133
struct NodeWriter<'s> {
3234
parent_scope: &'s AnyScope,
3335
parent: &'s Element,
@@ -246,27 +248,86 @@ impl BList {
246248

247249
// Step 2.2. Put the middle part back together in the new key order
248250
let mut replacements: Vec<BNode> = Vec::with_capacity((matching_len_start..lefts_to).len());
249-
// Roughly keep track of the order in which elements appear. If one appears out-of-order
250-
// we (over approximately) have to shift the element, otherwise it is guaranteed to be in place.
251-
let mut max_seen_idx = 0;
251+
// The goal is to shift as few nodes as possible.
252+
253+
// We handle runs of in-order nodes. When we encounter one out-of-order, we decide whether:
254+
// - to shift all nodes in the current run to the position after the node before of the run, or to
255+
// - "commit" to the current run, shift all nodes before the end of the run that we might
256+
// encounter in the future, and then start a new run.
257+
// Example of a run:
258+
// barrier_idx --v v-- end_idx
259+
// spliced_middle [ ... , M , N , C , D , E , F , G , ... ] (original element order)
260+
// ^---^-----------^ the nodes that are part of the current run
261+
// v start_writer
262+
// replacements [ ... , M , C , D , G ] (new element order)
263+
// ^-- start_idx
264+
let mut barrier_idx = 0; // nodes from spliced_middle[..barrier_idx] are shifted unconditionally
265+
struct RunInformation<'a> {
266+
start_writer: NodeWriter<'a>,
267+
start_idx: usize,
268+
end_idx: usize,
269+
}
270+
let mut current_run: Option<RunInformation<'_>> = None;
271+
252272
for l in lefts
253273
.drain(matching_len_start..) // lefts_to.. has been drained
254274
.rev()
255275
{
256-
let bundle = match spare_bundles.take(key!(l)) {
257-
Some(KeyedEntry(idx, mut r_bundle)) => {
258-
if idx < max_seen_idx {
259-
writer.shift(&mut r_bundle);
276+
let ancestor = spare_bundles.take(key!(l));
277+
// Check if we need to shift or commit a run
278+
if let Some(run) = current_run.as_mut() {
279+
if let Some(KeyedEntry(idx, _)) = ancestor {
280+
// If there are only few runs, this is a cold path
281+
if idx < run.end_idx {
282+
// Have to decide whether to shift or commit the current run. A few calculations:
283+
// A perfect estimate of the amount of nodes we have to shift if we move this run:
284+
let run_length = replacements.len() - run.start_idx;
285+
// A very crude estimate of the amount of nodes we will have to shift if we commit the run:
286+
// Note nodes of the current run should not be counted here!
287+
let estimated_skipped_nodes = run.end_idx - idx.max(barrier_idx);
288+
// double run_length to counteract that the run is part of the estimated_skipped_nodes
289+
if 2 * run_length > estimated_skipped_nodes {
290+
// less work to commit to this run
291+
barrier_idx = 1 + run.end_idx;
292+
} else {
293+
// Less work to shift this run
294+
for r in replacements[run.start_idx..].iter_mut().rev() {
295+
run.start_writer.shift(r);
296+
}
297+
}
298+
current_run = None;
260299
}
261-
max_seen_idx = usize::max(max_seen_idx, idx);
262-
writer = writer.patch(l, &mut r_bundle);
263-
r_bundle
264300
}
265-
None => {
266-
let (next_writer, bundle) = writer.add(l);
267-
writer = next_writer;
268-
bundle
301+
}
302+
let bundle = if let Some(KeyedEntry(idx, mut r_bundle)) = ancestor {
303+
match current_run.as_mut() {
304+
// hot path
305+
// We know that idx >= run.end_idx, so this node doesn't need to shift
306+
Some(run) => run.end_idx = idx,
307+
None => {
308+
match idx.cmp(&barrier_idx) {
309+
// peep hole optimization, don't start a run as the element is already where it should be
310+
Ordering::Equal => barrier_idx += 1,
311+
// shift the node unconditionally, don't start a run
312+
Ordering::Less => writer.shift(&mut r_bundle),
313+
// start a run
314+
Ordering::Greater => {
315+
current_run = Some(RunInformation {
316+
start_writer: writer.clone(),
317+
start_idx: replacements.len(),
318+
end_idx: idx,
319+
})
320+
}
321+
}
322+
}
269323
}
324+
writer = writer.patch(l, &mut r_bundle);
325+
r_bundle
326+
} else {
327+
// Even if there is an active run, we don't have to modify it
328+
let (next_writer, bundle) = writer.add(l);
329+
writer = next_writer;
330+
bundle
270331
};
271332
replacements.push(bundle);
272333
}

0 commit comments

Comments
 (0)