Skip to content
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

Closed
wants to merge 3 commits into from

Conversation

mahkoh
Copy link
Contributor

@mahkoh mahkoh commented Dec 21, 2020

The motivating example is

let mut vec = Vec::new_in(RcAlloc::new());
file.read_to_end(&mut vec)?;
let rc: Rc<[u8]> = vec.into_boxed_slice().into();

But note that read_to_end does not yet support allocators.

@rust-highfive
Copy link
Collaborator

r? @shepmaster

(rust-highfive has picked a reviewer for you, use r? to override)

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Dec 21, 2020
@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@mahkoh mahkoh force-pushed the zero-copy-rc branch 3 times, most recently from a46da63 to a136653 Compare December 22, 2020 00:14
@mahkoh
Copy link
Contributor Author

mahkoh commented Dec 22, 2020

I have a few more changes to StructAlloc

  • Replacing the type parameter by a const Layout parameter
  • Thoroughly documenting the safety

but it's essentially complete.

@bors
Copy link
Contributor

bors commented Dec 29, 2020

☔ The latest upstream changes (presumably #80449) made this pull request unmergeable. Please resolve the merge conflicts.

@shepmaster
Copy link
Member

r? @KodrAus

@rust-highfive rust-highfive assigned KodrAus and unassigned shepmaster Jan 1, 2021
@KodrAus
Copy link
Contributor

KodrAus commented Jan 14, 2021

cc @rust-lang/wg-allocators I think I'll need a hand reviewing this allocator implementation

@CAD97
Copy link
Contributor

CAD97 commented Jan 14, 2021

This is a really interesting use of the Allocator API. I need to think a bit more on the implications, but at first overview, the concept seems sound.

@JohnCSimon
Copy link
Member

Ping from triage
@mahkoh - can you please address the merge conflicts? Thank you.

@rustbot label: -S-waiting-on-review +S-waiting-on-author

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Feb 1, 2021
@mahkoh
Copy link
Contributor Author

mahkoh commented Feb 1, 2021

Ping from triage
@mahkoh - can you please address the merge conflicts? Thank you.

Once @CAD97 has performed his review. As I mentioned in my previous comment, there are still some very minor changes I'll make anyway.

@KodrAus
Copy link
Contributor

KodrAus commented Feb 10, 2021

r? @TimDiekmann

(@CAD97 is currently unavailable)

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();
Copy link
Member

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.

Copy link
Contributor Author

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>);
Copy link
Member

@TimDiekmann TimDiekmann Feb 10, 2021

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> {
Copy link
Member

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

Copy link
Contributor Author

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.

Copy link
Member

@TimDiekmann TimDiekmann Feb 10, 2021

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

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.

Copy link
Contributor Author

@mahkoh mahkoh Feb 10, 2021

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.

Copy link
Member

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.

Copy link
Contributor Author

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 of T in Rc<T> is known.

T can be a DST.

Copy link
Contributor Author

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]> {
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@TimDiekmann
Copy link
Member

While I don't know, how often the example in the OP is used, I really like the change to Rc and Arc, as it moves the logic to a better suited place. When the merge conflicts are solved, we should use a perf-run. Also it would be nice to compare the performance of Layout::extend and the manual implementation. For statically sized types, this should be optimized out.

@mahkoh mahkoh force-pushed the zero-copy-rc branch 2 times, most recently from 9124de7 to 965a688 Compare February 10, 2021 17:45
@rust-log-analyzer

This comment has been minimized.

@KodrAus
Copy link
Contributor

KodrAus commented Feb 10, 2021

@bors try @rust-timer queue

@rust-timer
Copy link
Collaborator

Awaiting bors try build completion.

@rustbot label: +S-waiting-on-perf

@bors bors added the S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. label Mar 8, 2021
@bors
Copy link
Contributor

bors commented Mar 8, 2021

⌛ Testing commit bad1cda with merge d68e8dd19a7080dd5a26b849837fa1f04151c753...

/// let contents: Rc<[u32]> = contents.into_boxed_slice().into();
/// ```
#[derive(Debug)]
#[unstable(feature = "struct_alloc", issue = "none")]
Copy link
Member

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.

@m-ou-se m-ou-se added the T-libs Relevant to the library team, which will review and decide on the PR/issue. label Mar 8, 2021
@bors
Copy link
Contributor

bors commented Mar 8, 2021

💥 Test timed out

@bors bors added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. labels Mar 8, 2021
@Dylan-DPC-zz
Copy link

@bors retry

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Mar 8, 2021
@rust-log-analyzer
Copy link
Collaborator

A job failed! Check out the build log: (web) (plain)

Click to see the possible cause of the failure (guessed by this bot)

@TimDiekmann
Copy link
Member

I'd say r- until a tracking issue is opened? cc @m-ou-se

@Amanieu
Copy link
Member

Amanieu commented Mar 8, 2021

@bors r-

@bors bors added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. labels Mar 8, 2021
@crlf0710
Copy link
Member

@mahkoh Ping from triage, would you mind creating a tracking issue as mentioned above? Or would you use some help?

@BurntSushi
Copy link
Member

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.

@TimDiekmann
Copy link
Member

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))
Copy link
Member

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.

@bors
Copy link
Contributor

bors commented May 6, 2021

☔ The latest upstream changes (presumably #84266) made this pull request unmergeable. Please resolve the merge conflicts.

@Amanieu
Copy link
Member

Amanieu commented May 6, 2021

Closing in favor of #84338

@Amanieu Amanieu closed this May 6, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.