-
Notifications
You must be signed in to change notification settings - Fork 13.6k
Description
Currently both Vec
s and VecDeque
s truncate
implementations just call pop_back()
in a loop. When std::mem::needs_drop::<T>()
is false
this is pretty inefficient.
Now LLVM does optimize away the loop on -O3
for Vec::truncate
, however this does not happen for VecDeque::truncate
.
Despite previous complaints, this exact issue has been wont-fix'd under the unofficial policy that we do not include -O0 exclusive optimizations in rust.
I would like to re-open the discussion with the following observations, in the hope that @alexcrichton might change his mind:
-
No matter the optimization level, the optimization never happens for
VecDeque::truncate
. -
The optimization is not just a constant factor speedup, it's an asymptotic speedup changing an O(n) time operation into O(1) where O(1) could easily be guaranteed. The difference in speed could literally be several orders of magnitude.
-
The optimizer is always fickle and ever-changing. Someone designing an algorithm using default Rust building blocks needs to rely on at least asymptotic complexities not suddenly regressing due to the optimizer not catching some pattern. Updating your Rust compiler should never have a chance to turn an O(n) algorithm into an O(n^2) one.
-
It pushes people towards unsafe code. In the linked complaint above the user ended up using the unsafe
set_len
when the safetruncate
could've exactly done the job.
In addition to the above, VecDeque
has truncate
, but is missing truncate_front
. If we make these truncate operations O(1) time it should also get a truncate_front
operation.
Without these changes it is impossible to implement certain algorithms using the default Rust collections for no particular good reason. For example, if I have a v: VecDeque<f64>
that keeps its elements sorted, it is currently impossible to write a O(log n) time routine that removes all elements less than x
in v
. We can find how many elements we need to remove from the front in O(log n) time, but removing them needlessly take O(n) time.
Activity
alexcrichton commentedon Jul 8, 2019
To clarify, all previous cases are purely -O0 optimizations. If this doesn't optimize at higher optimization levels then we can of course change the code to fix that. If this is only a -O0 optimization then that's different.
orlp commentedon Jul 8, 2019
@alexcrichton I just tested it and the
Vec::truncate
operation also doesn't optimize away onopt-level=1
, only 2 and higher. So even that portion isn't purely -O0.But even for -O0 I think we shouldn't leave O(n) -> O(1) reductions on the table when they cost only a couple lines of code. Debug performance matters.
silverweed commentedon Sep 14, 2019
+1: having a non-guaranteed O(1)
truncate
pretty much defeats the purpose of having a deque in many cases. And I agree that atruncate_front
(O(1) as well) would come very handy to have built-in for VecDeque.drdozer commentedon Oct 2, 2019
I was just bitten by this. Had assumed that
truncate()
was an O(1) op for Vec.gendx commentedon Feb 27, 2020
+1 for having a
truncate_front
as well.And somewhat similarly, it would be useful to have a
prepend
method as the symmetric version ofappend
, as well as anExtendFront
as a symmetric version ofstd::iter::Extend
. I'm not sure whether it makes sense to track those as separate issues.the8472 commentedon Jun 6, 2020
That seems to be fixed since 1.41
truncate_front
API forVecDeque
that is O(1) for trivial drop types rust-lang/libs-team#533