Remove thread_spawn for evaluation altogether
This allows to avoid a deadlock when there is only one Rayon thread, and doesn't sacrifice performance, since the caller of .get_result() had to always block on the iterator to be finished anyway, and all the messages are already sent from separate threads.
This commit is contained in:
parent
567a3c6b1d
commit
8c1c9e29d8
1 changed files with 9 additions and 28 deletions
|
|
@ -13,7 +13,7 @@ use crate::png::STD_WINDOW;
|
||||||
use crate::rayon;
|
use crate::rayon;
|
||||||
use crate::Deadline;
|
use crate::Deadline;
|
||||||
#[cfg(feature = "parallel")]
|
#[cfg(feature = "parallel")]
|
||||||
use crossbeam_channel::{bounded, unbounded, Receiver, Sender};
|
use crossbeam_channel::{unbounded, Receiver, Sender};
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
#[cfg(not(feature = "parallel"))]
|
#[cfg(not(feature = "parallel"))]
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
@ -45,44 +45,24 @@ pub(crate) struct Evaluator {
|
||||||
deadline: Arc<Deadline>,
|
deadline: Arc<Deadline>,
|
||||||
nth: AtomicUsize,
|
nth: AtomicUsize,
|
||||||
best_candidate_size: Arc<AtomicMin>,
|
best_candidate_size: Arc<AtomicMin>,
|
||||||
/// images are sent to the thread for evaluation
|
/// images are sent to the caller thread for evaluation
|
||||||
#[cfg(feature = "parallel")]
|
#[cfg(feature = "parallel")]
|
||||||
eval_send: Sender<Candidate>,
|
eval_channel: (Sender<Candidate>, Receiver<Candidate>),
|
||||||
// the thread helps evaluate images asynchronously
|
|
||||||
#[cfg(feature = "parallel")]
|
|
||||||
eval_thread: Receiver<Option<Candidate>>,
|
|
||||||
// in non-parallel mode, images are evaluated synchronously
|
// in non-parallel mode, images are evaluated synchronously
|
||||||
#[cfg(not(feature = "parallel"))]
|
#[cfg(not(feature = "parallel"))]
|
||||||
eval_best_candidate: RefCell<Option<Candidate>>,
|
eval_best_candidate: RefCell<Option<Candidate>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Like `std::thread::spawn`, but uses Rayon to create a thread.
|
|
||||||
//
|
|
||||||
// This allows this function to work on targets like WebAssembly,
|
|
||||||
// where `std::thread::spawn` doesn't work but Rayon can thanks to its
|
|
||||||
// support for custom spawn handlers.
|
|
||||||
//
|
|
||||||
// Unlike `std::thread::spawn`, `rayon::spawn` doesn't support closure results
|
|
||||||
// (it doesn't return a `JoinHandle<T>`), so we simulate it with a crossbeam
|
|
||||||
// channel instead (where `Receiver` acts as a `JoinHandle`).
|
|
||||||
fn thread_spawn<T: Send + 'static>(f: impl FnOnce() -> T + Send + 'static) -> Receiver<T> {
|
|
||||||
let (tx, rx) = bounded(0);
|
|
||||||
rayon::spawn(move || tx.send(f()).unwrap());
|
|
||||||
rx
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Evaluator {
|
impl Evaluator {
|
||||||
pub fn new(deadline: Arc<Deadline>) -> Self {
|
pub fn new(deadline: Arc<Deadline>) -> Self {
|
||||||
#[cfg(feature = "parallel")]
|
#[cfg(feature = "parallel")]
|
||||||
let (tx, rx) = unbounded();
|
let eval_channel = unbounded();
|
||||||
Self {
|
Self {
|
||||||
deadline,
|
deadline,
|
||||||
best_candidate_size: Arc::new(AtomicMin::new(None)),
|
best_candidate_size: Arc::new(AtomicMin::new(None)),
|
||||||
nth: AtomicUsize::new(0),
|
nth: AtomicUsize::new(0),
|
||||||
#[cfg(feature = "parallel")]
|
#[cfg(feature = "parallel")]
|
||||||
eval_send: tx,
|
eval_channel,
|
||||||
#[cfg(feature = "parallel")]
|
|
||||||
eval_thread: thread_spawn(move || rx.into_iter().min_by_key(Candidate::cmp_key)),
|
|
||||||
#[cfg(not(feature = "parallel"))]
|
#[cfg(not(feature = "parallel"))]
|
||||||
eval_best_candidate: RefCell::new(None),
|
eval_best_candidate: RefCell::new(None),
|
||||||
}
|
}
|
||||||
|
|
@ -92,8 +72,9 @@ impl Evaluator {
|
||||||
/// Or `None` if all reductions were worse than baseline.
|
/// Or `None` if all reductions were worse than baseline.
|
||||||
#[cfg(feature = "parallel")]
|
#[cfg(feature = "parallel")]
|
||||||
fn get_best_candidate(self) -> Option<Candidate> {
|
fn get_best_candidate(self) -> Option<Candidate> {
|
||||||
drop(self.eval_send); // disconnect the sender, breaking the loop in the thread
|
let (eval_send, eval_recv) = self.eval_channel;
|
||||||
self.eval_thread.recv().expect("eval thread")
|
drop(eval_send); // disconnect the sender, breaking the loop in the thread
|
||||||
|
eval_recv.into_iter().min_by_key(Candidate::cmp_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "parallel"))]
|
#[cfg(not(feature = "parallel"))]
|
||||||
|
|
@ -123,7 +104,7 @@ impl Evaluator {
|
||||||
// sends it off asynchronously for compression,
|
// sends it off asynchronously for compression,
|
||||||
// but results will be collected via the message queue
|
// but results will be collected via the message queue
|
||||||
#[cfg(feature = "parallel")]
|
#[cfg(feature = "parallel")]
|
||||||
let eval_send = self.eval_send.clone();
|
let eval_send = self.eval_channel.0.clone();
|
||||||
rayon::spawn(move || {
|
rayon::spawn(move || {
|
||||||
let filters_iter = STD_FILTERS.par_iter().with_max_len(1);
|
let filters_iter = STD_FILTERS.par_iter().with_max_len(1);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue