use rand::rngs::SmallRng;
use rand::{RngCore, SeedableRng};
use rayon::prelude::*;
use rayon::{ThreadPool, ThreadPoolBuilder};
use std::cell::{Cell, RefCell};
thread_local! {
static TLS_RNG: RefCell<Option<SmallRng>> = RefCell::new(None);
static TLS_WORKER_IDX: Cell<Option<usize>> = Cell::new(None);
}
#[inline]
fn splitmix64(mut x: u64) -> u64 {
x = x.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = x;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
#[inline]
fn mix_seed(global_seed: u64, worker_index: usize) -> u64 {
let x = global_seed
^ 0xD6E8_FEB8_6659_FD93u64
^ (worker_index as u64).wrapping_mul(0x9E37_79B1_85EB_CA87);
splitmix64(x)
}
pub fn build_rng_thread_pool(global_seed: u64, num_threads: Option<usize>) -> ThreadPool {
let mut builder = ThreadPoolBuilder::new();
if let Some(n) = num_threads {
builder = builder.num_threads(n);
}
builder
.start_handler(move |idx| {
let seed = mix_seed(global_seed, idx);
TLS_RNG.with(|cell| {
*cell.borrow_mut() = Some(SmallRng::seed_from_u64(seed));
});
TLS_WORKER_IDX.with(|c| c.set(Some(idx)));
})
.exit_handler(|_| {
TLS_RNG.with(|cell| *cell.borrow_mut() = None);
TLS_WORKER_IDX.with(|c| c.set(None));
})
.build()
.expect("Failed to build deterministic RNG thread pool")
}
pub fn install_with_rng_pool<F, R>(global_seed: u64, num_threads: Option<usize>, f: F) -> R
where
F: FnOnce() -> R + Send,
R: Send,
{
let pool = build_rng_thread_pool(global_seed, num_threads);
pool.install(f)
}
pub fn with_thread_rng<T>(f: impl FnOnce(&mut SmallRng) -> T) -> T {
TLS_RNG.with(|cell| {
let mut opt = cell.borrow_mut();
let rng = opt
.as_mut()
.expect("with_thread_rng() called outside a seeded pool. Use install_with_rng_pool().");
f(rng)
})
}
pub fn worker_index() -> Option<usize> {
TLS_WORKER_IDX.with(|c| c.get())
}
pub fn derive_salt(global_seed: u64, label: &str, extra: u64) -> u64 {
let mut h = global_seed ^ 0xA076_1D64_78BD_642F;
h = splitmix64(h ^ extra.rotate_left(17));
for &b in label.as_bytes() {
h = splitmix64(h ^ b as u64);
}
h
}
pub fn par_for_each_indexed<T, F>(data: &[T], func: F)
where
T: Sync,
F: Fn(usize, &T) + Send + Sync,
{
data.par_iter()
.enumerate()
.for_each(|(i, item)| func(i, item));
}
pub fn next_subseed() -> u64 {
with_thread_rng(|rng| rng.next_u64())
}
#[deprecated(
note = "Use build_rng_thread_pool(...).install(|| ...) or install_with_rng_pool(...). \n init_thread_rng seeded only one worker and was racy across pools."
)]
pub fn init_thread_rng(_global_seed: u64) {
}
#[deprecated(
note = "Use install_with_rng_pool(global_seed, num_threads, f). \n The old function seeded only one worker in the scope."
)]
pub fn parallel_scope_with_rng<F: FnOnce() + Send + Sync>(_global_seed: u64, f: F) {
rayon::scope(|_| f());
}
#[cfg(feature = "mpi-support")]
pub mod onizuka_partitioning {
use super::*;
use crate::partitioning::binpack::Item;
use crate::partitioning::graph_traits::PartitionableGraph;
use crate::partitioning::{PartitionMap, PartitionerConfig, PartitionerError};
pub fn parallel_merge_clusters_into_parts(
items: &[Item],
cfg: &PartitionerConfig,
) -> Result<Vec<usize>, PartitionerError> {
let mut part_assignment = None;
let mut error = None;
install_with_rng_pool(cfg.rng_seed, None, || {
match crate::partitioning::binpack::merge_clusters_into_parts(
items,
cfg.n_parts,
cfg.epsilon,
) {
Ok(assign) => part_assignment = Some(assign),
Err(e) => error = Some(e),
}
});
if let Some(e) = error {
Err(e)
} else {
Ok(part_assignment.expect("Parallel merge did not produce a result"))
}
}
pub fn parallel_build_vertex_cuts<G>(
graph: &G,
pm: &PartitionMap<G::VertexId>,
salt: u64,
) -> (Vec<usize>, Vec<Vec<(G::VertexId, usize)>>)
where
G: PartitionableGraph<VertexId = usize> + Sync,
{
let mut result = None;
install_with_rng_pool(salt, None, || {
result = Some(crate::partitioning::vertex_cut::build_vertex_cuts(
graph, pm, salt,
));
});
result
.expect("Parallel vertex cut did not produce a result")
.expect("vertex cut failed")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tls_rng_initialized_on_all_workers() {
install_with_rng_pool(12345, Some(4), || {
(0..10_000).into_par_iter().for_each(|_| {
let _ = with_thread_rng(|rng| rng.next_u64());
});
});
}
#[test]
fn deterministic_across_runs_same_threads() {
let run = |seed| -> Vec<u64> {
install_with_rng_pool(seed, Some(1), || {
(0..1000)
.map(|_| with_thread_rng(|rng| rng.next_u64()))
.collect::<Vec<_>>()
})
};
let a = run(777);
let b = run(777);
assert_eq!(a, b);
}
#[test]
fn worker_index_exposed() {
install_with_rng_pool(1, Some(2), || {
(0..1000).into_par_iter().for_each(|_| {
assert!(worker_index().is_some());
});
});
assert!(worker_index().is_none());
}
}