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

Make Rc<T>::deref and Arc<T>::deref zero-cost #132553

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

EFanZh
Copy link
Contributor

@EFanZh EFanZh commented Nov 3, 2024

Currently, Rc<T> and Arc<T> store pointers to RcInner<T> and ArcInner<T>. This PR changes the pointers so that they point to T directly instead.

This is based on the assumption that we access the T value more frequently than accessing reference counts. With this change, accessing the data can be done without offsetting pointers from RcInner<T> and ArcInner<T> to their contained data. This change might also enables some possibly useful future optimizations, such as:

  • Convert &[Rc<T>] into &[&T] within O(1) time.
  • Convert &[Rc<T>] into Vec<&T> utilizing memcpy.
  • Convert &Option<Rc<T>> into Option<&T> without branching.
  • Make Rc<T> and Arc<T> FFI compatible types where T: Sized.

@rustbot
Copy link
Collaborator

rustbot commented Nov 3, 2024

r? @jhpratt

rustbot has assigned @jhpratt.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Nov 3, 2024
@EFanZh EFanZh force-pushed the zero-cost-rc-arc-deref branch from b283c44 to ae36f44 Compare November 3, 2024 09:14
@rust-log-analyzer

This comment has been minimized.

@marmeladema
Copy link
Contributor

Would it potentially enable those types to have an ffi compatible ABI? So that they could be returned and passed directly from /to ffi function, like Box?

@rust-log-analyzer

This comment has been minimized.

@EFanZh
Copy link
Contributor Author

EFanZh commented Nov 3, 2024

Would it potentially enable those types to have an ffi compatible ABI? So that they could be returned and passed directly from /to ffi function, like Box?

I think in theory it is possible, at least for sized types, but I am not familiar with how to formally make it so.

@EFanZh EFanZh force-pushed the zero-cost-rc-arc-deref branch from ae36f44 to 0d6165f Compare November 3, 2024 11:21
@rust-log-analyzer

This comment has been minimized.

@EFanZh EFanZh force-pushed the zero-cost-rc-arc-deref branch from 0d6165f to 98edd5b Compare November 3, 2024 13:06
@rust-log-analyzer

This comment has been minimized.

@jhpratt
Copy link
Member

jhpratt commented Nov 3, 2024

r? libs

@rustbot rustbot assigned joboet and unassigned jhpratt Nov 3, 2024
@EFanZh EFanZh force-pushed the zero-cost-rc-arc-deref branch from 98edd5b to 8beb51d Compare November 4, 2024 16:29
@rust-log-analyzer

This comment has been minimized.

@EFanZh EFanZh force-pushed the zero-cost-rc-arc-deref branch from 8beb51d to d7879fa Compare November 4, 2024 17:26
@rust-log-analyzer

This comment has been minimized.

@EFanZh EFanZh force-pushed the zero-cost-rc-arc-deref branch from d7879fa to 317aa0e Compare November 4, 2024 18:40
@joboet
Copy link
Member

joboet commented Nov 7, 2024

@EFanZh Is this ready for review? If so, please un-draft the PR.

@EFanZh
Copy link
Contributor Author

EFanZh commented Nov 7, 2024

@joboet: The source code part is mostly done, but I haven’t finished updating LLDB and CDB pretty printers. The CI doesn’t seem to run those tests.

@joboet
Copy link
Member

joboet commented Nov 8, 2024

No worries! I just didn't want to keep you waiting in case you had forgotten to change the state.
@rustbot 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 Nov 8, 2024
@EFanZh EFanZh force-pushed the zero-cost-rc-arc-deref branch 3 times, most recently from f243654 to 1308bf6 Compare November 11, 2024 18:35
@EFanZh EFanZh force-pushed the zero-cost-rc-arc-deref branch 2 times, most recently from ce3dab7 to 3641396 Compare December 22, 2024 04:26
@rust-log-analyzer

This comment has been minimized.

@EFanZh EFanZh force-pushed the zero-cost-rc-arc-deref branch from 3641396 to 9a531fc Compare December 22, 2024 05:36
@EFanZh
Copy link
Contributor Author

EFanZh commented Dec 22, 2024

I have rewritten this PR as a proof of concept for the idea I mentioned in #132553 (comment). Specifically, I added an alloc::raw_rc module for storing common logic between Rc and Arc. In this way, it could be easier to add new features, optimizations and bugfixes for both Rc and Arc. I also adopted @scottmcm’s method of storing reference counts adjacent to value field. Here is a brief description of some important types I added:

/// Shared reference counts type for both `Rc` and `Arc`.
/// The `UnsafeCell<usize>` type is used as `Cell<usize>` for `Rc`
/// and `AtomicUsize` for `Arc`.
pub struct RefCounts {
    pub weak: UnsafeCell<usize>,
    pub strong: UnsafeCell<usize>,
}

/// Base implementation for `rc::Weak` and `sync::Weak`.
pub struct RawWeak<T, A>
where
    T: ?Sized,
{
    /// Dangling or points to the value field of an rc allocation.
    ptr: NonNull<T>,
    alloc: A,
}

/// Base implementation for `rc::Rc` and `sync::Arc`.
#[repr(transparent)]
pub struct RawRc<T, A>
where
    T: ?Sized,
{
    /// A `RawRc` is just a `RawWeak` with a strong reference owned by itself.
    weak: RawWeak<T, A>,
    _phantom_data: PhantomData<T>,
}

/// Base implementation for `rc::UniqueRc` and possible future `sync::UniqueArc`.
#[repr(transparent)]
pub struct RawUniqueRc<T, A>
where
    T: ?Sized,
{
    /// A `RawUniqueRc` is just a `RawWeak` with an initialized value but has a
    /// strong reference count of zero.
    weak: RawWeak<T, A>,
}

With these types above, the standard library types are implemented as:

mod rc {
    #[repr(transparent)]
    pub struct Weak<T: ?Sized, A: Allocator = Global> {
        raw_weak: RawWeak<T, A>,
    }

    #[repr(transparent)]
    pub struct Rc<T: ?Sized, A: Allocator = Global> {
        raw_rc: RawRc<T, A>,
    }

    #[repr(transparent)]
    pub struct UniqueRc<T: ?Sized, A: Allocator = Global> {
        raw_unique_rc: RawUniqueRc<T, A>,
    }
}

mod sync {
    #[repr(transparent)]
    pub struct Weak<T: ?Sized, A: Allocator = Global> {
        raw_weak: RawWeak<T, A>,
    }

    #[repr(transparent)]
    pub struct Arc<T: ?Sized, A: Allocator = Global> {
        raw_rc: RawRc<T, A>,
    }
}

The different behaviors of rc and sync types are represented with an RcOps trait:

pub trait RcOps {
    /// Used by `RawRc::clone` and `RawWeak::clone`.
    unsafe fn increment_ref_count(count: &UnsafeCell<usize>);

    /// Used by `RawRc::drop` and `RawWeak::drop`.
    unsafe fn decrement_ref_count(count: &UnsafeCell<usize>) -> bool;

    /// Used by `RawWeak::upgrade`.
    unsafe fn upgrade(strong_count: &UnsafeCell<usize>) -> bool;

    /// Used by `RawRc::downgrade`.
    unsafe fn downgrade(weak_count: &UnsafeCell<usize>);

    /// Used by `RawRc::try_unwrap`.
    unsafe fn lock_strong_count(strong_count: &UnsafeCell<usize>) -> bool;

    /// Used by `RawUniqueRc::into_rc`.
    unsafe fn unlock_strong_count(strong_count: &UnsafeCell<usize>);

    /// Used by `RawRc::get_mut`.
    unsafe fn is_unique(strong_count: &UnsafeCell<usize>, weak_count: &UnsafeCell<usize>) -> bool;

    /// Used by `RawRc::make_mut`.
    #[cfg(not(no_global_oom_handling))]
    unsafe fn make_unique<T, A, F, G>(rc: &mut RawRc<T, A>, by_clone: F, by_move: G)
    where
        T: ?Sized,
        F: FnOnce(&mut RawRc<T, A>),
        G: FnOnce(&mut RawRc<T, A>);
}

Both rc and sync modules have their own RcOps implementation types. Their implementation types are passed to raw_rc module as generic arguments. For example:

mod raw_rc {
  impl<T, A> RawRc<T, A> where T: ?Sized {
      pub unsafe fn clone<R>(&self) -> Self where A: Clone, R: RcOps {
          unsafe {
              R::increment_ref_count(self.strong_count());
  
              Self::from_weak(self.weak.clone_without_increment_weak_count())
          }
      }
  }
}

mod rc {
    enum RcOps {}

    /// Specifies behaviors of `rc::Rc`, `rc::Weak` and `rc::UniqueRc`.
    impl raw_rc::RcOps for RcOps { ... }

    impl<T: ?Sized, A: Allocator + Clone> Clone for Rc<T, A> {
        fn clone(&self) -> Self {
            Self { raw_rc: unsafe { self.raw_rc.clone::<RcOps>() } }
        }
    }
}

mod sync {
    enum RcOps {}

    /// Specifies behaviors of `sync::Arc` and `sync::Weak`.
    impl raw_rc::RcOps for RcOps { ... }

    impl<T: ?Sized, A: Allocator + Clone> Clone for Arc<T, A> {
        fn clone(&self) -> Self {
            Self { raw_rc: unsafe { self.raw_rc.clone::<RcOps>() } }
        }
    }
}

This PR is not a completed work:

  • Documentations are missing.
  • Binary size and compilation time haven’t been optimized.
  • #[inline] and #[track_caller] attributes haven’t been considered.
  • Names of types, traits and functions are somewhat arbitrarily decided, better names may needed to be used.
  • Hidden bugs may exist.
  • Commit history needs to be reorganized for merging.

Now, I think this PR is in a good shape to decide whether it is something the standard library wants. @joboet, @scottmcm: what do you think? Is it worth completing this PR?

@safinaskar
Copy link
Contributor

I think in theory it is possible, at least for sized types, but I am not familiar with how to formally make it so.

If you really want to do this, then you should place allocator in allocated block, not in Rc itself. And then you can add #[repr(transparent)] to RawWeak. And then Rc will became totally ABI-compatible with raw pointer (see here for more details https://doc.rust-lang.org/nightly/std/primitive.fn.html#abi-compatibility ). Also, this will mean that you don't need to clone allocator when you do Rc::clone()

@safinaskar
Copy link
Contributor

Ping @scottmcm (see my prev. comment). Also, here allocators for Rc was originally introduced: #89132 and here discussed: https://rust-lang.zulipchat.com/#narrow/channel/197181-t-libs.2Fwg-allocators/topic/Perf.20for.20.2389132.20.28Arc.2FRc.20allocator_api.29

@EFanZh
Copy link
Contributor Author

EFanZh commented Dec 29, 2024

If you really want to do this, then you should place allocator in allocated block, not in Rc itself.

@safinaskar: Personally, I am fine with the idea, but I think it’s better to implement it in a separate PR, since it will introduce API changes (For example, Arc::<T,A>::into_raw_with_allocator may additionally require A being Clone).

As for #[repr(transparent)] support, it can also be done later with some unknown method (maybe compiler magic, some new attribute like #[repr(transparent_if_possible)], or moving the allocator into the allocated block like you suggested).

@rust-log-analyzer

This comment has been minimized.

@EFanZh EFanZh force-pushed the zero-cost-rc-arc-deref branch from 6e1cefc to 8a7689b Compare December 29, 2024 06:52
@rust-log-analyzer

This comment has been minimized.

@EFanZh EFanZh force-pushed the zero-cost-rc-arc-deref branch from 8a7689b to b939b01 Compare December 29, 2024 10:14
@rust-log-analyzer

This comment has been minimized.

@EFanZh EFanZh force-pushed the zero-cost-rc-arc-deref branch from b939b01 to d4c3550 Compare December 29, 2024 11:18
@rust-log-analyzer

This comment has been minimized.

@EFanZh EFanZh force-pushed the zero-cost-rc-arc-deref branch 3 times, most recently from 2f8c95f to 6f78608 Compare January 4, 2025 09:00
@rust-log-analyzer

This comment has been minimized.

@EFanZh EFanZh force-pushed the zero-cost-rc-arc-deref branch 3 times, most recently from dbe51dd to e4c2742 Compare January 4, 2025 12:54
@EFanZh EFanZh force-pushed the zero-cost-rc-arc-deref branch from e4c2742 to 873f27a Compare January 6, 2025 14:45
@rust-log-analyzer

This comment has been minimized.

@EFanZh EFanZh force-pushed the zero-cost-rc-arc-deref branch from 873f27a to 454eccf Compare January 6, 2025 15:57
@rust-log-analyzer

This comment has been minimized.

@EFanZh EFanZh force-pushed the zero-cost-rc-arc-deref branch from 454eccf to e37e38b Compare January 6, 2025 18:15
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-compiler Relevant to the compiler team, which will review and decide on the PR/issue. 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.

10 participants