diff --git a/src/algo/llp/mod.rs b/src/algo/llp/mod.rs index b579e3de..ca03b10a 100644 --- a/src/algo/llp/mod.rs +++ b/src/algo/llp/mod.rs @@ -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); }); } diff --git a/src/utils/sync_slice.rs b/src/utils/sync_slice.rs index fde1d7e0..ea95d2ea 100644 --- a/src/utils/sync_slice.rs +++ b/src/utils/sync_slice.rs @@ -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` has the same memory layout and caveats as [`Cell`], but it +/// is [`Sync`] if its content is. In particular, if [`Cell`] has the same +/// in-memory representation as its inner type `T`, then `SyncCell` 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` 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` 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]`, 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`. /// -/// Data races will cause undefined behaviour. -pub struct SyncSlice<'a, T>(&'a [Cell]); -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` come from [`Deref`] to [`Cell`]. 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` 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(Cell); +unsafe impl Sync for SyncCell {} + +impl SyncCell { + /// 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) { + 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 SyncCell { + /// Returns a `&SyncCell` 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` has the same memory layout as `Cell`. + &*(Cell::from_mut(value) as *const Cell as *const Self) } +} - /// Returns an element of the slice. - /// - /// # Panics - /// - /// Panics if the index is not within bounds. +#[allow(trivial_casts)] +impl SyncCell<[T]> { + /// Returns a `&[SyncCell]` 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] { + let slice_of_cells = Deref::deref(self).as_slice_of_cells(); + // SAFETY: `SyncCell` has the same memory layout as `Cell` + unsafe { &*(slice_of_cells as *const [Cell] as *const [SyncCell]) } } } -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 std::ops::Deref for SyncCell { + type Target = Cell; + #[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 Clone for SyncCell { + #[inline] + fn clone(&self) -> SyncCell { + 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 PartialEq for SyncCell { + #[inline] + fn eq(&self, other: &SyncCell) -> bool { + self.get() == other.get() + } +} + +impl Eq for SyncCell {} + +impl PartialOrd for SyncCell { + #[inline] + fn partial_cmp(&self, other: &SyncCell) -> Option { + 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) -> 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) -> 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) -> 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) -> 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 Ord for SyncCell { + #[inline] + fn cmp(&self, other: &SyncCell) -> 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`. +pub trait SyncSlice { + /// Returns a view of the + unsafe fn as_sync_slice(&mut self) -> &[SyncCell]; } -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 for [T] { + unsafe fn as_sync_slice(&mut self) -> &[SyncCell] { + SyncCell::from_mut(self).as_slice_of_cells() } }