-
Notifications
You must be signed in to change notification settings - Fork 13.2k
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
[RFC] Add allocators for zero-copy conversion from Box<T> into Rc<T> #80273
Conversation
r? @shepmaster (rust-highfive has picked a reviewer for you, use r? to override) |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
a46da63
to
a136653
Compare
I have a few more changes to
but it's essentially complete. |
☔ The latest upstream changes (presumably #80449) made this pull request unmergeable. Please resolve the merge conflicts. |
r? @KodrAus |
cc @rust-lang/wg-allocators I think I'll need a hand reviewing this allocator implementation |
This is a really interesting use of the |
r? @TimDiekmann |
unsafe fn deallocate(&self, data_ptr: NonNull<u8>, data_layout: Layout) { | ||
unsafe { | ||
let struct_ptr = Self::data_ptr_to_struct_ptr(data_ptr, data_layout); | ||
let struct_layout = Self::struct_layout(data_layout).unwrap(); |
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.
Instead of unwrap
we better use except
here. When calling deallocate
according to the safety section, this should never fail. However, if the method is called wrong by accident, the user should see a useful message.
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.
Done
/// When this allocator creates an allocation for layout `layout`, the pointer can be | ||
/// offset by `-offsetof(Struct, data)` and the resulting pointer points is an allocation | ||
/// of `A` for `Layout::new::<Struct>()`. | ||
pub(crate) struct StructAlloc<T, A = Global>(A, PhantomData<*const T>); |
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 is basically a prefix-allocator, or more generally an affix-allocator. While the crate is a bit outdated due to personal time issues, you might want to take a look at alloc_compose::Affix
.
} | ||
|
||
/// Computes the layout of `Struct`. | ||
fn struct_layout(data_layout: Layout) -> Result<Layout, AllocError> { |
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.
Do you have a specific reason, why you dont' use Layout::extend
? See Affix::allocation_layout
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.
At the time I wasn't aware of this function. Either way, this function is written to optimize performance given that we know that the layout of T
will be known at compile time.
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.
The compiler is able to optimize out Layout::extend
, when T
is known at compile time: https://godbolt.org/z/Tx5zv8
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.
Compare https://godbolt.org/z/Mon6j1
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.
In the Rc
use case, data_layout
is also known at compile time. Anyway, the allocator may be used in other places as well, so it would be better to use Layout::extend
and optimize that function instead. I don't see a point in duplicating code logic, when there is a function for that exact use case.
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.
In the Rc use case, data_layout is also known at compile time.
It is not. data_layout is the layout passed to the allocator.
so it would be better to use Layout::extend and optimize that function instead
I do not know if it is possible in general to optimize this function. There are different formulas to calculate padding etc., each optimized for different cases. If the compiler were able to tell the optimizer that the alignment is always a power of two, then those formulas might be optimized identically. But the compiler does not do this at this time. Note that in my optimized function I use two different formulas to minimize the number of runtime-only values.
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 is not. data_layout is the layout passed to the allocator.
Yes, but the allocator is only called behind Rc
, and the type of T
in Rc<T>
is known. Actually, I just noticed, StructAlloc::allocate
is never called when constructing (A)Rc
, which should be changed.
If the compiler were able to tell the optimizer that the alignment is always a power of two and > 0, then those formulas might be optimized identically.
I opened a PR a while ago: #75281 but it wasn't merged, maybe we should try it again.
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.
Yes, but the allocator is only called behind
Rc
, and the type ofT
inRc<T>
is known.
T can be a DST.
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 opened a PR a while ago: #75281 but it wasn't merged, maybe we should try it again.
I experimented with this a while ago and LLVM did not make use of such assertions.
unsafe fn struct_ptr_to_data_ptr( | ||
struct_ptr: NonNull<[u8]>, | ||
data_layout: Layout, | ||
) -> NonNull<[u8]> { |
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.
Is this working well on ZSTs? We might want a test for this.
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.
Done
While I don't know, how often the example in the OP is used, I really like the change to |
9124de7
to
965a688
Compare
This comment has been minimized.
This comment has been minimized.
@bors try @rust-timer queue |
Awaiting bors try build completion. @rustbot label: +S-waiting-on-perf |
⌛ Testing commit bad1cda with merge d68e8dd19a7080dd5a26b849837fa1f04151c753... |
/// let contents: Rc<[u32]> = contents.into_boxed_slice().into(); | ||
/// ``` | ||
#[derive(Debug)] | ||
#[unstable(feature = "struct_alloc", issue = "none")] |
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 is missing a tracking issue.
💥 Test timed out |
@bors retry |
I'd say r- until a tracking issue is opened? cc @m-ou-se |
@bors r- |
@mahkoh Ping from triage, would you mind creating a tracking issue as mentioned above? Or would you use some help? |
Moderation note: mahkoh is no longer participating in the Rust community. It looks like this PR is about ready to merge, so if someone wanted to pick it up where the OP left off, that would be very helpful. Otherwise, we should close this PR. |
Thank you for the information! I'm going to make some small changes and make a separate pull request. I'd leave this open until I open the PR. This will probably be in the next two weeks. |
let old_struct_ptr = Self::data_ptr_to_struct_ptr(old_data_ptr, old_data_layout); | ||
let new_struct_ptr = | ||
self.0.$id(old_struct_ptr, old_struct_layout, new_struct_layout)?; | ||
Ok(Self::struct_ptr_to_data_ptr(new_struct_ptr, new_data_layout)) |
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 not take into account, if the alignment changes. This may result in a different offset between the prefix and the actual data so storing the data may be required. As it's not needed for this use case, I'll skip this for now.
☔ The latest upstream changes (presumably #84266) made this pull request unmergeable. Please resolve the merge conflicts. |
Closing in favor of #84338 |
The motivating example is
But note that
read_to_end
does not yet support allocators.