Skip to content

Commit

Permalink
New SyncCell
Browse files Browse the repository at this point in the history
  • Loading branch information
vigna committed Nov 24, 2024
1 parent ffe0561 commit 0dcf9c4
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 127 deletions.
4 changes: 2 additions & 2 deletions src/algo/llp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,8 +408,8 @@ fn combine(result: &mut [usize], labels: &[usize], temp_perm: &mut [usize]) -> R
}

pub fn invert_permutation(perm: &[usize], inv_perm: &mut [usize]) {
let sync_slice = unsafe { SyncSlice::from_mut(inv_perm) };
let sync_slice = unsafe { inv_perm.as_sync_slice() };
perm.par_iter().enumerate().for_each(|(i, &x)| {
sync_slice.set(x, i);
sync_slice[x].set(i);
});
}
273 changes: 148 additions & 125 deletions src/utils/sync_slice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,169 +4,192 @@
* SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
*/

use std::cell::Cell;
use std::{cell::Cell, cmp::Ordering, ops::Deref};

/// Synchronized slice reference that can be passed to multiple threads,
/// permitting, nonetheless, interior mutability of its elements.
/// A mutable memory location that is [`Sync`].
///
/// In some cases, multiple threads access a slice in way that cannot cause data
/// races: for example, if each thread accesses a different element of the
/// slice. In such cases, by [turning a mutable reference to the
/// slice](SyncSlice::from_mut) in a [`SyncSlice`], you can pass a reference to
/// the slice to multiple threads, but still modify its elements using interior
/// mutability. Note that for this to work the elements of the slice must be
/// [`Send`].
/// # Memory layout
///
/// As in the case of [`Cell`]:
/// - For types that implement [`Copy`], the [`get`](Cell::get) method
/// retrieves the current value of an element of the slice by duplicating it.
/// - For types that implement [`Default`], the [`take`](Cell::take) method
/// replaces the current value of an element of the slice with
/// [`Default::default()`] and returns the replaced value.
/// - All types have:
/// - [`replace`](Cell::replace): replaces the current value of an element of
/// the slice and returns the replaced value.
/// - [`set`](Cell::set): replaces the value of an element of the slice,
/// dropping the replaced value.
/// `SyncCell<T>` has the same memory layout and caveats as [`Cell<T>`], but it
/// is [`Sync`] if its content is. In particular, if [`Cell<T>`] has the same
/// in-memory representation as its inner type `T`, then `SyncCell<T>` has the
/// same in-memory representation as its inner type `T` (but the code does not
/// rely on this).
///
/// All methods have an unsafe variant that does not do bounds checking.
/// `SyncCell<T>` is useful when you need to share a mutable memory location
/// across threads, and you rely on the fact that the intended behavior will not
/// cause data races. For example, the content will be written once and then
/// read many times, in this order.
///
/// An [extension trait](SyncSliceExt) provides a [convenient conversion
/// method](SyncSliceExt::as_sync_slice).
/// The main usage of `SyncCell<T>` is to be to able to write to different
/// locations of a slice in parallel, leaving the control of data races to the
/// user, without the access cost of an atomic variable. For this purpose,
/// `SyncCell` implements the [`as_slice_of_cells`](SyncCell::as_slice_of_cells)
/// method, which turns a reference to `SyncCell<[T]>` into a reference to
/// `[SyncCell<T>]`, similarly to the [analogous method of
/// `Cell`](Cell::as_slice_of_cells).
///
/// # Undefined Behavior
/// Since this is the most common usage, the extension trait [`SyncSlice`] adds
/// to slices an unsafe method [`as_sync_slice`](SyncSlice::as_sync_slice) that
/// turns a mutable reference to a slice of `T` into a reference to a slice of
/// `SyncCell<T>`.
///
/// Data races will cause undefined behaviour.
pub struct SyncSlice<'a, T>(&'a [Cell<T>]);
unsafe impl<'a, T: Send> Sync for SyncSlice<'a, T> {}

impl<'a, T> SyncSlice<'a, T> {
/// Creates a new synchronized slice from a mutable reference.
///
/// # Safety
///
/// It is the caller's responsibility to ensure that no data
/// races occur.
///
/// # Undefined Behavior
///
/// Data races will cause undefined behaviour.
/// # Methods
///
/// Most methods of `SyncCell<T>` come from [`Deref`] to [`Cell<T>`]. Some have
/// been reimplemented: in particular, [`new`](SyncCell::new) and
/// [`from_mut`](SyncCell::from_mut) are unsafe, since they force [`Sync`]. Both
/// [`swap`](SyncCell::swap) and [`into_inner`](SyncCell::into_inner) are
/// reimplemented as the [`Deref`] version would not work.
///
/// `SyncCell` implements almost all traits implemented by [`Cell`] by
/// delegation for convenience, but some, as [`Default`], cannot be implemented
/// because they would use unsafe methods.
///
/// # Examples
///
/// In this example, you can see that `SyncCell<T>` enables mutation across
/// threads.
///
/// ```
/// use webgraph::utils::SyncCell;
/// use webgraph::utils::SyncSlice;
///
/// let mut x = 0;
/// let c = unsafe { SyncCell::new(x) };
///
/// let mut v = vec![1, 2, 3, 4];
/// let s = unsafe { v.as_sync_slice() };
///
/// std::thread::scope(|scope| {
/// scope.spawn(|| {
/// // You can use interior mutability in another thread
/// c.set(5);
/// });
///
/// scope.spawn(|| {
/// // You can use interior mutability in another thread
/// s[0].set(5);
/// });
/// scope.spawn(|| {
/// // You can use interior mutability in another thread
/// // on the same slice
/// s[1].set(10);
/// });
/// });
/// ```
#[repr(transparent)]
pub struct SyncCell<T: ?Sized>(Cell<T>);
unsafe impl<T: Sync> Sync for SyncCell<T> {}

impl<T> SyncCell<T> {
/// Creates a new `Cell` containing the given value.
#[inline(always)]
pub unsafe fn from_mut(slice: &'a mut [T]) -> Self {
Self(Cell::from_mut(slice).as_slice_of_cells())
pub unsafe fn new(value: T) -> Self {
Self(Cell::new(value))
}

/// Unwraps the value, consuming the cell.
pub fn into_inner(self) -> T {
self.0.into_inner()
}

/// Swaps the values of two `SyncCell`s by delegation to [`Cell::swap`].
pub fn swap(&self, other: &SyncCell<T>) {
self.0.swap(other);
}
}

impl<'a, T: Copy> SyncSlice<'a, T> {
/// Returns an element of the slice, without doing bounds checking.
///
/// # Safety
///
/// The index must be smaller than the length of the slice.
impl<T: ?Sized> SyncCell<T> {
/// Returns a `&SyncCell<T>` from a `&mut T`
#[allow(trivial_casts)]
#[inline(always)]
pub unsafe fn get_unchecked(&self, index: usize) -> T {
self.0.get_unchecked(index).get()
pub unsafe fn from_mut(value: &mut T) -> &Self {
// SAFETY: `SyncCell<T>` has the same memory layout as `Cell<T>`.
&*(Cell::from_mut(value) as *const Cell<T> as *const Self)
}
}

/// Returns an element of the slice.
///
/// # Panics
///
/// Panics if the index is not within bounds.
#[allow(trivial_casts)]
impl<T> SyncCell<[T]> {
/// Returns a `&[SyncCell<T>]` from a `&SyncCell<[T]>`
#[inline(always)]
pub fn get(&self, index: usize) -> T {
self.0[index].get()
pub fn as_slice_of_cells(&self) -> &[SyncCell<T>] {
let slice_of_cells = Deref::deref(self).as_slice_of_cells();
// SAFETY: `SyncCell<T>` has the same memory layout as `Cell<T>`
unsafe { &*(slice_of_cells as *const [Cell<T>] as *const [SyncCell<T>]) }
}
}

impl<'a, T: Default> SyncSlice<'a, T> {
/// Takes the value of an element of the slice, leaving `Default::default()`
/// in its place, without doing bounds checking.
///
/// # Safety
///
/// The index must be smaller than the length of the slice.
impl<T: ?Sized> std::ops::Deref for SyncCell<T> {
type Target = Cell<T>;

#[inline(always)]
pub unsafe fn take_unchecked(&self, index: usize) -> T {
self.0.get_unchecked(index).take()
fn deref(&self) -> &Self::Target {
&self.0
}
}

/// Takes the value of an element of the slice, leaving `Default::default()`
/// in its place.
///
/// # Panics
///
/// Panics if the index is not within bounds.
#[inline(always)]
pub fn take(&self, index: usize) -> T {
self.0[index].take()
impl<T: Copy> Clone for SyncCell<T> {
#[inline]
fn clone(&self) -> SyncCell<T> {
unsafe { SyncCell::new(self.get()) }
}
}

impl<'a, T> SyncSlice<'a, T> {
/// Sets an element of the slice to `value`, without doing bounds
/// checking.
///
/// # Safety
///
/// The index must be smaller than the length of the slice.
#[inline(always)]
pub unsafe fn set_unchecked(&self, index: usize, value: T) {
self.0.get_unchecked(index).set(value)
impl<T: PartialEq + Copy> PartialEq for SyncCell<T> {
#[inline]
fn eq(&self, other: &SyncCell<T>) -> bool {
self.get() == other.get()
}
}

impl<T: Eq + Copy> Eq for SyncCell<T> {}

impl<T: PartialOrd + Copy> PartialOrd for SyncCell<T> {
#[inline]
fn partial_cmp(&self, other: &SyncCell<T>) -> Option<Ordering> {
self.get().partial_cmp(&other.get())
}

/// Sets an element of the slice to `value`.
///
/// # Panics
///
/// Panics if the index is not within bounds.
#[inline(always)]
pub fn set(&self, index: usize, value: T) {
self.0[index].set(value)
#[inline]
fn lt(&self, other: &SyncCell<T>) -> bool {
self.get() < other.get()
}

/// Replaces the contained value with `value`, and returns the old contained
/// value, without doing bounds checking.
///
/// # Safety
///
/// The index must be smaller than the length of the slice.
#[inline(always)]
pub unsafe fn replace_unchecked(&self, index: usize, value: T) -> T {
self.0.get_unchecked(index).replace(value)
#[inline]
fn le(&self, other: &SyncCell<T>) -> bool {
self.get() <= other.get()
}

/// Replaces the contained value with `value`, and returns the old contained
/// value.
///
/// # Panics
///
/// Panics if the index is not within bounds.
#[inline(always)]
pub fn replace(&self, index: usize, value: T) -> T {
self.0[index].replace(value)
#[inline]
fn gt(&self, other: &SyncCell<T>) -> bool {
self.get() > other.get()
}

/// Returns the length of the slice.
pub fn len(&self) -> usize {
self.0.len()
#[inline]
fn ge(&self, other: &SyncCell<T>) -> bool {
self.get() >= other.get()
}
}

/// Returns [`true`] if the slice has a length of 0.`
pub fn is_empty(&self) -> bool {
self.0.is_empty()
impl<T: Ord + Copy> Ord for SyncCell<T> {
#[inline]
fn cmp(&self, other: &SyncCell<T>) -> Ordering {
self.get().cmp(&other.get())
}
}

/// Extension trait providing a [synchronized view](SyncSlice) of a slice via
/// the [`as_sync_slice`](SyncSliceExt::as_sync_slice) method.
pub trait SyncSliceExt<'a, T: Copy> {
/// Invokes [`SyncSlice::from_mut`] on the slice.
unsafe fn as_sync_slice(&'a mut self) -> SyncSlice<'a, T>;
/// Extension trait turning (unsafely) a slice of `T` into a slice of
/// `SyncCell<T>`.
pub trait SyncSlice<T> {
/// Returns a view of the
unsafe fn as_sync_slice(&mut self) -> &[SyncCell<T>];
}

impl<'a, T: Copy> SyncSliceExt<'a, T> for [T] {
unsafe fn as_sync_slice(&'a mut self) -> SyncSlice<'a, T> {
SyncSlice::from_mut(self)
impl<'a, T> SyncSlice<T> for [T] {
unsafe fn as_sync_slice(&mut self) -> &[SyncCell<T>] {
SyncCell::from_mut(self).as_slice_of_cells()
}
}

0 comments on commit 0dcf9c4

Please sign in to comment.