From 0c5cca9f968823749e50979b837ed1c317403828 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Wed, 4 May 2016 15:08:24 -0700 Subject: [PATCH] WIP Add a steal_half method to sync::chase_lev Questions: * We could change the signature to make callers supply a vec, rather than allocate our own in `steal_half`. This could potentially be more efficient if the caller is repeatedly retrying. * Is this safe? I think so, but I'm not 100% sure. The paper mentions that it would be interesting to apply a steal-half operation to this deque, but they didn't do it. I couldn't find any other papers or libraries that implemented this operation either, although my google-fu may be weak in this regard. The most important part, as far as I can tell, is populating the results array before performing the CAS to ensure that we get the correct set of resulting items (after the CAS, the circular buffer could circle back over the slots we're stealing and overwrite them with new values). Everything seems to be pretty much the same as stealing one item, which leads to my next question. * Should we factor out the common parts between `steal` and `steal_half`? There's a ton of duplication here, as I've authored it. If you think this is valuable, then I can write some tests and fix this up. --- src/sync/chase_lev.rs | 53 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/sync/chase_lev.rs b/src/sync/chase_lev.rs index f72a8ea92..dfa58e04a 100644 --- a/src/sync/chase_lev.rs +++ b/src/sync/chase_lev.rs @@ -102,6 +102,19 @@ pub enum Steal { Data(T), } +/// When stealing half of the data, this is an enumeration of the possible +/// outcomes. +#[derive(PartialEq, Eq, Debug)] +pub enum StealHalf { + /// The deque was empty at the time of stealing. + Empty, + /// The stealer lost the race for stealing data, and a retry may return more + /// data. + Abort, + /// The stealer has successfully stolen some data. + Data(Vec), +} + // An internal buffer used by the chase-lev deque. This structure is actually // implemented as a circular buffer, and is used as the intermediate storage of // the data in the deque. @@ -137,6 +150,12 @@ impl Stealer { pub fn steal(&self) -> Steal { self.deque.steal() } + + /// Steals half of the work off the end of the queue (opposite of the + /// worker's end) + pub fn steal_half(&self) -> StealHalf { + self.deque.steal_half() + } } impl Clone for Stealer { @@ -254,6 +273,40 @@ impl Deque { } } + fn steal_half(&self) -> StealHalf { + let guard = epoch::pin(); + + let t = self.top.load(Acquire); + fence(SeqCst); + let b = self.bottom.load(Acquire); + + let size = b - t; + if size <= 0 { + return StealHalf::Empty; + } + + let half = size + 1 / 2; + + unsafe { + let a = self.array.load(Acquire, &guard).unwrap(); + + let mut data = Vec::with_capacity(half as usize); + for i in t..t + half { + data.push(a.get(i)); + } + + if self.top.compare_and_swap(t, t + half, SeqCst) == t { + StealHalf::Data(data) + } else { + while data.len() > 0 { + mem::forget(data.pop()); + } + StealHalf::Abort + } + } + + } + // potentially shrink the array. This can be called only from the worker. unsafe fn maybe_shrink(&self, b: isize, t: isize, guard: &epoch::Guard) { let a = self.array.load(SeqCst, guard).unwrap();