Change:
let mut g = BackshiftOnDrop { v: self, processed_len: 0, deleted_cnt: 0, original_len }; <del> // process_one return a bool indicates whether the processing element should be retained. #[inline(always)] fn process_one<F, T, A: Allocator, const DELETED: bool>( </del> <ins> fn process_loop<F, T, A: Allocator, const DELETED: bool>( original_len: usize, </ins> f: &mut F, g: &mut BackshiftOnDrop<'_, T, A>, <del> ) -> bool where </del> <ins> ) where </ins> F: FnMut(&mut T) -> bool, { <del> // SAFETY: Unchecked element must be valid. let cur = unsafe { &mut *g.v.as_mut_ptr().add(g.processed_len) }; if !f(cur) { // Advance early to avoid double drop if `drop_in_place` panicked. g.processed_len += 1; g.deleted_cnt += 1; // SAFETY: We never touch this element again after dropped. unsafe { ptr::drop_in_place(cur) }; // We already advanced the counter. return false; } if DELETED { // SAFETY: `deleted_cnt` > 0, so the hole slot must not overlap with current element. // We use copy for move, and never touch this element again. unsafe { let hole_slot = g.v.as_mut_ptr().add(g.processed_len - g.deleted_cnt); ptr::copy_nonoverlapping(cur, hole_slot, 1); </del> <ins> while g.processed_len != original_len { // SAFETY: Unchecked element must be valid. let cur = unsafe { &mut *g.v.as_mut_ptr().add(g.processed_len) }; if !f(cur) { // Advance early to avoid double drop if `drop_in_place` panicked. g.processed_len += 1; g.deleted_cnt += 1; // SAFETY: We never touch this element again after dropped. unsafe { ptr::drop_in_place(cur) }; // We already advanced the counter. if DELETED { continue; } else { break; } } if DELETED { // SAFETY: `deleted_cnt` > 0, so the hole slot must not overlap with current element. // We use copy for move, and never touch this element again. unsafe { let hole_slot = g.v.as_mut_ptr().add(g.processed_len - g.deleted_cnt); ptr::copy_nonoverlapping(cur, hole_slot, 1); } </ins> } <ins> g.processed_len += 1; </ins> } <del> g.processed_len += 1; return true; </del> } // Stage 1: Nothing was deleted. <del> while g.processed_len != original_len { if !process_one::<F, T, A, false>(&mut f, &mut g) { break; } } </del> <ins> process_loop::<F, T, A, false>(original_len, &mut f, &mut g); </ins> // Stage 2: Some elements were deleted. <del> while g.processed_len != original_len { process_one::<F, T, A, true>(&mut f, &mut g); } </del> <ins> process_loop::<F, T, A, true>(original_len, &mut f, &mut g); </ins> // All item are processed. This can be optimized to `set_len` by LLVM. drop(g);

Discussion:
I noticed today that using is much slower than filtering and allocating a new Vector. I realize the former is probably more memory efficient, but I still found it surprising that it would be that much slower (or really slower at all). When testing with this code: on 1.59.0-nightly ( 2021-12-01), I get these benchmark results: on a Ryzen 5900X running Linux. Testing on a different machine (Xeon E3-1271 v3), I get similar numbers: seemed like the obvious choice to me, so it being slower is either a bug or should be documented somewhere. <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
i7 8700k windows 10 default configuration: with lto = true rustc 1.59.0-nightly ()
Setting makes the disparity even bigger for me:
It looks like is optimized () for small or large probabilities of keeping elements, i.e. long runs of elements that will be either kept or deleted. on the other hand just reads elements one by one, filters them and writes out those that passed, i.e. it does not care about runs. Since the test-case here drops every second element it's the worst possible case for , possibly even worse than random selection (since random clusters may have runs). Edit: Hrm, the benchmarks look like they're optimizing for high/low retain probabilities; but the code doesn't, it still moves one element at a time when previous ones have been dropped.
Edit: Doh, but at least now we now that caused some major slowdown.
You're not copying the most recent version of retain. has gained a const bool parameter in and thus gets instantiated in two flavors.
I tried a few scenarios that should heavily favor but the effect remains. i7 8700k w10 rustc 1.59.0-nightly ()
I'll take a stab at this. claim
Submitted , if anyone wants to confirm my benchmark results.