Simultaneous mutable access to arbitrary indices of a large vector that are guaranteed to be disjoint

后端 未结 4 1013
谎友^
谎友^ 2020-12-11 16:16

Context

I have a case where multiple threads must update objects stored in a shared vector. However, the vector is very large, and the number of elements to update

4条回答
  •  醉梦人生
    2020-12-11 16:50

    I think this is a reasonable place to use unsafe code. The logic itself is safe but cannot be checked by the compiler because it relies on knowledge outside of the type system (the contract of BTreeSet, which itself relies on the implementation of Ord and friends for usize).

    In this sample, we preemptively bounds check all the indices via range, so each call to add is safe to use. Since we take in a set, we know that all the indices are disjoint, so we aren't introducing mutable aliasing. It's important to get the raw pointer from the slice to avoid aliasing between the slice itself and the returned values.

    use std::collections::BTreeSet;
    
    fn uniq_refs<'i, 'd: 'i, T>(
        data: &'d mut [T],
        indices: &'i BTreeSet,
    ) -> impl Iterator + 'i {
        let start = data.as_mut_ptr();
        let in_bounds_indices = indices.range(0..data.len());
    
        // I copied this from a Stack Overflow answer
        // without reading the text that explains why this is safe
        in_bounds_indices.map(move |&i| unsafe { &mut *start.add(i) })
    }
    
    use std::iter::FromIterator;
    
    fn main() {
        let mut scores = vec![1, 2, 3];
    
        let selected_scores: Vec<_> = {
            // The set can go out of scope after we have used it.
            let idx = BTreeSet::from_iter(vec![0, 2]);
            uniq_refs(&mut scores, &idx).collect()
        };
    
        for score in selected_scores {
            *score += 1;
        }
    
        println!("{:?}", scores);
    }
    

    Once you have used this function to find all the separate mutable references, you can use Rayon to modify them in parallel:

    use rayon::prelude::*; // 1.0.3
    
    fn example(scores: &mut [i32], indices: &BTreeSet) {
        let selected_scores: Vec<_> = uniq_refs(scores, indices).collect();
        selected_scores.into_par_iter().for_each(|s| *s *= 2);
    
        // Or
    
        uniq_refs(scores, indices).par_bridge().for_each(|s| *s *= 2);
    }
    

    You may wish to consider using a bitset instead of a BTreeMap to be more efficient, but this answer uses only the standard library.

    See also:

    • How do I use Rayon with an existing iterator?

提交回复
热议问题