-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Specialize TrustedLen
for Iterator::unzip()
#123253
Conversation
This feels like a |
Don't check the capacity every time (and also for `Extend` for tuples, as this is how `unzip()` is implemented). I did this with a semi-public (`#[doc(hidden)]` public, so collections outside of core can implement it) specialization trait that has a method (`extend_one_unchecked()`) that doesn't check for growth. Then specialize `Extend for (A, B)` for `TrustedLen` to call it, and specialize it for collections (only `Vec` and `VecDequq` from the collection in the standard library could benefit from this) An alternative way of implementing this is to have this method on `Extend` directly, but this was rejected by @the8472 in a review (rust-lang#123253 (comment)). I didn't mark the trait as unsafe; a concern that may arise from that is that implementing `extend_one_unchecked()` correctly must also incur implementing `extend_reserve()`, otherwise you can have UB. This is a somewhat non-local safety invariant. However, I believe this is fine, since to have actual UB you must have unsafe code inside your `extend_one_unchecked()` that makes incorrect assumption, *and* not implement `extend_reserve()`. I've also documented this requirement.
Don't check the capacity every time (and also for `Extend` for tuples, as this is how `unzip()` is implemented). I did this with a semi-public (`#[doc(hidden)]` public, so collections outside of core can implement it) specialization trait that has a method (`extend_one_unchecked()`) that doesn't check for growth. Then specialize `Extend for (A, B)` for `TrustedLen` to call it, and specialize it for collections (only `Vec` and `VecDequq` from the collection in the standard library could benefit from this) An alternative way of implementing this is to have this method on `Extend` directly, but this was rejected by @the8472 in a review (rust-lang#123253 (comment)). I didn't mark the trait as unsafe; a concern that may arise from that is that implementing `extend_one_unchecked()` correctly must also incur implementing `extend_reserve()`, otherwise you can have UB. This is a somewhat non-local safety invariant. However, I believe this is fine, since to have actual UB you must have unsafe code inside your `extend_one_unchecked()` that makes incorrect assumption, *and* not implement `extend_reserve()`. I've also documented this requirement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly looks fine to me and I can't think of a better approach given the current specialization limitations.
Just a few minor points
if lower_bound > 0 { | ||
a.extend_reserve(lower_bound); | ||
b.extend_reserve(lower_bound); | ||
} | ||
|
||
iter.fold((), extend(a, b)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does essentially the same as the specialized impl, the only difference is the case where the upper bound exceeds usize.
They could be unified (just doing the panic separately) or the default impl could be more like Vec's extend_desugared, but that doesn't have to happen in this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see a reason to unify them. It will take more work and LOC that the few duplicated LOC.
Don't check the capacity every time (and also for `Extend` for tuples, as this is how `unzip()` is implemented). I did this with an unsafe method on `Extend` that doesn't check for growth (`extend_one_unchecked()`). I've marked it as perma-unstable currently, although we may want to expose it in the future so collections outside of std can benefit from it. Then specialize `Extend for (A, B)` for `TrustedLen` to call it. It may seem that an alternative way of implementing this is to have a semi-public trait (`#[doc(hidden)]` public, so collections outside of core can implement it) for `extend()` inside tuples, and specialize it from collections. However, it is impossible due to limitations of `min_specialization`. A concern that may arise with the current approach is that implementing `extend_one_unchecked()` correctly must also incur implementing `extend_reserve()`, otherwise you can have UB. This is a somewhat non-local safety invariant. However, I believe this is fine, since to have actual UB you must have unsafe code inside your `extend_one_unchecked()` that makes incorrect assumption, *and* not implement `extend_reserve()`. I've also documented this requirement.
c4bfa05
to
54556f4
Compare
@rustbot ready Did the requested changes. |
unzip is used in a few places in the compiler, so it might affect perf results @bors r+ rollup=never |
☀️ Test successful - checks-actions |
Finished benchmarking commit (382148d): comparison URL. Overall result: no relevant changes - no action needed@rustbot label: -perf-regression Instruction countThis benchmark run did not return any relevant results for this metric. Max RSS (memory usage)Results (primary 2.0%, secondary 3.4%)This is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.
CyclesResults (secondary -3.3%)This is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.
Binary sizeThis benchmark run did not return any relevant results for this metric. Bootstrap: 699.867s -> 699.903s (0.01%) |
Don't check the capacity every time (and also for
Extend
for tuples, as this is howunzip()
is implemented).I did this with an unsafe method on
Extend
that doesn't check for growth (extend_one_unchecked()
). I've marked it as perma-unstable currently, although we may want to expose it in the future so collections outside of std can benefit from it. Then specializeExtend for (A, B)
forTrustedLen
to call it.An alternative way of implementing this is to have a semi-public trait (
#[doc(hidden)]
public, so collections outside of core can implement it) forextend()
inside tuples, and specialize it from collections. However:A concern that may arise with the current approach is that implementing
extend_one_unchecked()
correctly must also incur implementingextend_reserve()
, otherwise you can have UB. This is a somewhat non-local safety invariant. However, I believe this is fine, since to have actual UB you must have unsafe code inside yourextend_one_unchecked()
that makes incorrect assumption, and not implementextend_reserve()
. I've also documented this requirement.Benchmark:
Code:
Before:
After: