12

I have a massive vector that I want to be able to load/act on in parallel, e.g. load first hundred thousand indices in one thread, next in another and so on. As this is going to be a very hot part of the code, I have come up with this following proof of concept unsafe code to do this without Arcs and Mutexes:

let mut data:Vec<u32> = vec![1u32, 2, 3];
let head = data.as_mut_ptr();
let mut guards = (0..3).map(|i|
  unsafe {
    let mut target = std::ptr::Unique::new(head.offset(i));
    let guard = spawn(move || {
      std::ptr::write(target.get_mut(), 10 + i as u32);
    });
    guard
  });

Is there anything I have missed here that can make this potentially blow up?

This uses #![feature(unique)] so I don't see how to use this in stable. Is there a way to do this sort of thing in stable (ideally safely without using raw pointers and overhead of Arc's and Mutex's)?

Also, looking at documentation for Unique, it says

It also implies that the referent of the pointer should not be modified without a unique path to the Unique reference

I am not clear what "unique path" means.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
sgldiv
  • 623
  • 6
  • 16
  • 1
    Could `Vec.split_at_mut(..)` work for you? – llogiq Jul 27 '15 at 06:10
  • you can split it mutably but you are still stuck with how to move ownership into the worker threads and then get it back. i don't see how to do this safely since while the worker threads are computing, in the parent thread you still have mutable access to the original vec. there are lifetime issues too as the closure may escape the block, unboxed closures may fix that - i don't know much about them though. – sgldiv Jul 27 '15 at 08:12
  • 1
    once [`scoped`](https://doc.rust-lang.org/nightly/std/thread/fn.scoped.html) gets a proper replacement `split_at_mut` is the correct solution. Until then I suggest simply creating multiple vectors, one for every thread. – oli_obk Jul 27 '15 at 08:48
  • 2
    `chunks_mut` is a nicer version of `split_at_mut` for this purpose: `for target in data.chunks_mut(100_000) { ... }`. – huon Jul 27 '15 at 17:28
  • Maybe this approach is useful for you: https://stackoverflow.com/a/70851207/286335 – cibercitizen1 Jan 25 '22 at 15:25

2 Answers2

22

Today the rayon crate is the de facto standard for this sort of thing:

use rayon::prelude::*;

fn main() {
    let mut data = vec![1, 2, 3];
    data.par_iter_mut()
        .enumerate()
        .for_each(|(i, x)| *x = 10 + i as u32);
    assert_eq!(vec![10, 11, 12], data);
}

Note that this is just one line different from the single-threaded version using standard iterators, which would replace par_iter_mut with iter_mut.

See also Writing a small ray tracer in Rust and Zig.

Jack O'Connor
  • 10,068
  • 4
  • 48
  • 53
  • How can you limit parallelism with this (like do not make more than 4 API requests at a time while iterating over a list of requests to make)? – Brandon Ros Oct 10 '22 at 18:46
  • @BrandonRos https://github.com/rayon-rs/rayon/blob/master/FAQ.md#how-many-threads-will-rayon-spawn – Jack O'Connor Oct 13 '22 at 06:55
8

One can use an external library for this, e.g. simple_parallel (disclaimer, I wrote it) allows one to write:

extern crate simple_parallel;

let mut data = vec![1u32, 2, 3, 4, 5];

let mut pool = simple_parallel::Pool::new(4);

pool.for_(data.chunks_mut(3), |target| {
    // do stuff with `target`
})

The chunks and chunks_mut methods are the perfect way to split a vector/slice of Ts into equally sized chunks: they respectively return an iterator over elements of type &[T] and &mut [T].

huon
  • 94,605
  • 21
  • 231
  • 225
  • Thanks! This is very helpful. Though I'd like to sort out all the details of this myself, so I can learn. I hadn't thought of doing this using channels. I will need to think about this some more. – sgldiv Jul 27 '15 at 21:55
  • 2
    I rolled my own with https://github.com/rust-lang/threadpool . http://huonw.github.io/blog/2015/05/finding-closure-in-rust/ was very helpful. – sgldiv Jul 29 '15 at 23:46