Допустим, мы хотим удвоить (на месте) каждый элемент в каждом из срезов вектора, где срезы определяются списком пар - (начальная, конечная) позиции. Следующий код идиоматически выражает намерение, но не компилируется из-за изменяемого заимствования вектора внутри параллели for_each:
use rayon::prelude::*;
fn main() {
let mut data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let slice_pairs = vec![(0, 3), (4, 7), (8, 10)];
slice_pairs.into_par_iter().for_each(|(start, end)| {
let slice = &mut data[start..end];
for elem in slice.iter_mut() {
*elem *= 2;
}
});
println!("{:?}", data);
}
Здесь существует реальная вероятность гонок данных — чтобы их исключить, нужно проверить, не перекрываются ли срезы. Вопрос в том, как лучше всего это сделать в Rust: с помощью небезопасного кода или безопасного API. В следующем коде unsafe используется для «сделай это»; мой вопрос в том, есть ли лучший способ, чем приведенный ниже (который преобразует базовый указатель вектора в i64 и обратно, чтобы «слепить» средство проверки заимствования на проблему.)
use rayon::prelude::*;
use std::mem;
fn main() {
let mut data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let slice_pairs = vec![(0, 4), (4, 7), (7, 10)];
let ptr_outer = data.as_mut_ptr();
let ptr_int : i64 = unsafe { mem::transmute(ptr_outer) };
slice_pairs.into_par_iter().for_each(|(start, end)| {
unsafe {
let ptr : *mut i32 = mem::transmute(ptr_int);
let slice = std::slice::from_raw_parts_mut(ptr.add(start), end - start);
for elem in slice.iter_mut() {
*elem *= 2;
}
}
});
println!("{:?}", data);
}
@AlexeyS.Larionov Не нужно выделять и копировать, есть AtomicX::from_mut_slice() (ночью, стабильно можно использовать небезопасный код).
Я отредактировал свой ответ, добавив непроверенную (и небезопасную) версию split_many(), которую можно использовать, если известно, что регионы не перекрываются.

Я бы предложил сначала преобразовать slice_pairs в последовательность изменяемых срезов, а затем использовать все эти срезы параллельно.
Разделение целого среза на несколько независимых подфрагментов (с точки зрения средства проверки заимствований) можно выполнить с помощью среза::split_at_mut().
Конечно, индексы в slice_pairs должны быть упорядочены и не должны перекрываться, чтобы эти подсрезы были правильными.
Обратите внимание, что я пытался использовать .map().collect() вместо явного цикла с .push(), чтобы построить последовательность срезов, но мне это не удалось...
Компилятор сообщил, что замыкание FnMut в .map() не может вернуть ссылку; может быть кто-то сможет исправить мой код...
use rayon::prelude::*;
fn main() {
let mut data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let slice_pairs = vec![(0, 3), (4, 7), (8, 10)];
// build a sequence of mutable slices
let mut slices = Vec::with_capacity(slice_pairs.len());
let mut remaining = data.as_mut_slice();
let mut idx = 0;
for (start, end) in slice_pairs {
let (_skip, tail) = remaining.split_at_mut(start - idx);
let (sl, tail) = tail.split_at_mut(end - start);
remaining = tail;
idx = end;
slices.push(sl);
}
println!("slices: {:?}", slices);
// parallel usage of the mutable slices
slices.into_par_iter().for_each(|sl| {
for elem in sl.iter_mut() {
*elem *= 2;
}
});
println!("data: {:?}", data);
}
/*
slices: [[1, 2, 3], [5, 6, 7], [9, 10]]
data: [2, 4, 6, 4, 10, 12, 14, 8, 18, 20]
*/
Я думаю, что split_at_mut или что-то подобное — это то, что вам нужно, и я думаю, что в Rayon нет ни split_according_to_a_list_of_pairs_mut, ни итератора, который «просто делает это». Спасибо!
@YossiKreinin Однако вы определенно можете написать split_according_to_a_list_of_pairs_mut() в безопасном Rust, используя split_at_mut() в качестве строительного блока. См. split_many() в решении, которое я написал до того, как понял, что prog-fh опередил меня: play.rust-lang.org/…
@user4815162342 user4815162342 спасибо, это выглядит хорошо, и я думаю, так и должно быть сделано в безопасном коде. Интересно, есть ли более чистый способ использовать небезопасную версию, которая, кстати, быстрее, поскольку ей не нужно сортировать фрагменты. (Чище, как без преобразования указателей или иным образом более лаконично)
@YossiKreinin Небезопасная версия требует сортировки фрагментов или проверки того, что они не перекрываются каким-либо другим образом. Единственная причина, по которой он сейчас работает быстро, заключается в том, что он совершенно не подходит для произвольного slice_pairs.
@YossiKreinin Я добавил свой код в качестве ответа, потому что отдельная реализация split_many() может быть полезна будущим посетителям.
Вы можете использовать split_at_mut(), чтобы разбить фрагмент на несколько фрагментов, используя безопасный код:
fn split_many<'a, T>(mut slice: &'a mut [T], regions: &[(usize, usize)]) -> Vec<&'a mut [T]> {
let mut regions = regions.to_vec();
regions.sort_by_key(|&(b, _e)| b);
let mut ret = vec![];
let mut offset = 0;
for (b, e) in regions {
assert!(b >= offset && e >= b); // prohibit overlaps
let (chosen, rest) = slice.split_at_mut(e - offset);
ret.push(&mut chosen[b - offset..]);
offset = e;
slice = rest;
}
ret
}
Имея этот помощник, вы можете выразить параллельную манипуляцию на месте «очевидным» способом:
fn main() {
let mut data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let slice_pairs = vec![(0, 3), (4, 7), (8, 10)];
split_many(&mut data, &slice_pairs)
.into_par_iter()
.for_each(|region| {
for elem in region.iter_mut() {
*elem *= 2;
}
});
println!("{:?}", data);
}
Обратите внимание: пока регионы представлены произвольными индексами, предоставленными во время выполнения, необходимо выполнить первоначальный проход через них, чтобы убедиться, что они не перекрываются (split_many() паникует, если обнаруживает перекрытие). Невыполнение этого требования было бы неразумно, поскольку простой выбор перекрывающихся регионов привел бы к неопределенному поведению. Однако если вы контролируете код, генерирующий регионы, и знаете, что они не перекрываются, вы можете создать более быструю небезопасную версию split_many(). Опираясь на внешние гарантии, ему не нужно ни сортировать, ни выделять новый набор регионов, и он может даже напрямую вернуть ParallelIterator:
/// Split `slice` into `regions` and iterate over them in parallel.
/// Safety: regions must not overlap.
unsafe fn split_many_unchecked<'a, T: Send + Sync>(
slice: &'a mut [T],
regions: &'a [(usize, usize)],
) -> impl ParallelIterator<Item = &'a mut [T]> + 'a {
struct Wrap<T>(*mut T);
unsafe impl<T> Sync for Wrap<T> {}
unsafe impl<T> Send for Wrap<T> {}
let slice = Wrap(slice.as_mut_ptr());
regions.par_iter().map(move |&(b, e)| {
let _ = &slice; // prevent closure from capturing slice.0
std::slice::from_raw_parts_mut(slice.0.add(b), e - b)
})
}
В случае удвоения есть другой способ рассмотрения, но O(N) в памяти - выделить вектор атомарных целых чисел с инициализацией 1 (это будут множители реальных данных), параллельно обрабатывать диапазоны, умножать атомарные значения (они обеспечивают синхронизировать). Затем запустите параллельное умножение фактических данных на множители по частям (без перекрытия, поэтому синхронизация не требуется).