-
Notifications
You must be signed in to change notification settings - Fork 183
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
Turn ZeroVec into a struct #2599
Conversation
Running norm benches (with non icu4x benches commented out) gives me: after this PR
before this PR
I did run the benches post-PR first, so the negative deltas are good. |
/// zerovec.with_mut(|v| v.push(12_u16.to_unaligned())); | ||
/// assert!(zerovec.is_owned()); | ||
/// ``` | ||
pub fn with_mut<R>(&mut self, f: impl FnOnce(&mut Vec<T::ULE>) -> R) -> R { |
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'm not a fan of the continuation-passing style. Many uses of this actually only need a mutable slice, which is something we can return. Other places, like in datagen, do things like pushing by repeatedly calling with_mut
which I think is not very readable. They should assemble a Vec and then convert it into a ZeroVec at the end.
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.
Good point, added. I didn't move people off of with_mut, this might be a change we can do later. Ultimately I think such an API is still valuable to have.
wasm build failed due to flakiness |
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.
Post-submit review
where | ||
T: AsULE + ?Sized, | ||
T: AsULE, |
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.
Question: Why did you drop the ?Sized
?
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.
It's completely useless, this is for Copy stack types, there's no way to have a ?Sized
type in ULE and have it work. The ULE methods won't be callable for such types.
Most of the ZeroVec methods also can't have ?Sized
bounds
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.
But this is the AsULE. I can't think of a specific use case, but it doesn't seem that T: Sized needs to be a 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.
AsULE returns owned values, so to do anything useful it has to be Sized
In general the idea is to be as generic as possible in trait bounds where more specific bounds are unnecessary, but I don't think that advice applies for opt out bounds like ?Sized
, "be as generic as possible" is shorthand for "don't use unnecessary bounds"
ZeroVec::Owned(vec) => &**vec, | ||
ZeroVec::Borrowed(slice) => *slice, | ||
}; | ||
let slice = unsafe { &*self.buf }; |
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.
Nit: Safety comment. It's not super clear what is being cast from and to.
unsafe impl<'a, T: AsULE> Send for ZeroVec<'a, T> where T::ULE: Send + Sync {} | ||
unsafe impl<'a, T: AsULE> Sync for ZeroVec<'a, T> where T::ULE: Sync {} |
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.
Nit: Safety docs. Why does T::ULE: Sync
imply ZeroVec<'a, T>: Sync
?
/// | ||
/// # Example | ||
/// | ||
/// ```rust,ignore |
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.
Nit: Don't ignore this example. Make it so that it runs and passes.
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.
huh, i wodner why this was ignore initially
/// ``` | ||
Borrowed(&'a [T::ULE]), | ||
/// Pointer to data | ||
buf: *mut [T::ULE], |
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.
Issue: Don't you need a manual Drop
impl to clean this up? (I think it can just call self.into_cow()
)
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.
oh shoot, yeah
@@ -433,10 +454,28 @@ where | |||
let ptr = ptr as *mut P::ULE; | |||
Vec::from_raw_parts(ptr, len, cap) |
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.
Suggestion: There are now multiple places where we convert back and forth between a vec and its raw parts (here, into_cow
, and new_owned
). Consider making them share code or at least use the same style with similar safety comments.
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.
They already share code as much as possible; we deconstruct the vec in one place (new_owned) and construct it once (into_cow). There's also try_into_converted but that's about constructing a vec of a different type
This avoids branches in most cases by avoiding the enum discriminant check, instead stuffing the discriminant into the zero-ness of the vec.
Supersedes #2554
Seems to give a ~10% improvement, but some of our benches are flaky.