initial commit
This commit is contained in:
commit
e32ea997f4
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/target
|
||||||
|
Cargo.lock
|
||||||
|
/.idea
|
10
Cargo.toml
Normal file
10
Cargo.toml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "jobqueue"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Harald Hoyer <harald@redhat.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rayon = "1.3.0"
|
232
src/lib.rs
Normal file
232
src/lib.rs
Normal file
|
@ -0,0 +1,232 @@
|
||||||
|
use rayon;
|
||||||
|
use rayon::iter::plumbing::{
|
||||||
|
bridge_unindexed, Consumer, Folder, UnindexedConsumer, UnindexedProducer,
|
||||||
|
};
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
pub struct JobQueueInner<'a, T> {
|
||||||
|
lifo: std::sync::RwLock<Vec<T>>,
|
||||||
|
handle_count: AtomicUsize,
|
||||||
|
state: PhantomData<&'a ()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> JobQueueInner<'a, T> {
|
||||||
|
fn new(lifo: Vec<T>) -> Self {
|
||||||
|
Self {
|
||||||
|
lifo: RwLock::new(lifo),
|
||||||
|
handle_count: AtomicUsize::new(0),
|
||||||
|
state: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push(&self, job: T) {
|
||||||
|
if let Ok(mut t) = self.lifo.write() {
|
||||||
|
t.push(job)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn done(&self, handle: &mut JobQueueHandle<T>) {
|
||||||
|
if let Ok(lock) = self.lifo.write() {
|
||||||
|
self.handle_count.fetch_sub(1, Ordering::Relaxed);
|
||||||
|
drop(lock);
|
||||||
|
}
|
||||||
|
handle.0.take();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a, T> Drop for JobQueueInner<'a, T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if self.handle_count.load(Ordering::Relaxed) != 0 {
|
||||||
|
panic!("JobQueueInner handle_count != 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct JobQueueHandle<'a, T>(Option<Arc<JobQueueInner<'a, T>>>);
|
||||||
|
|
||||||
|
impl<'a, T> JobQueueHandle<'a, T> {
|
||||||
|
pub fn push(&self, job: T) {
|
||||||
|
if let Some(q) = &self.0 {
|
||||||
|
q.push(job)
|
||||||
|
} else {
|
||||||
|
panic!("JobQueueHandle use after done()")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Drop for JobQueueHandle<'a, T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(q) = self.0.take() {
|
||||||
|
q.done(self);
|
||||||
|
} else {
|
||||||
|
panic!("JobQueueHandle has no queue!!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct JobQueue<'a, T>(Arc<JobQueueInner<'a, T>>);
|
||||||
|
|
||||||
|
impl<'a, T> JobQueue<'a, T> {
|
||||||
|
pub fn new(lifo: Vec<T>) -> Self {
|
||||||
|
Self(Arc::new(JobQueueInner::new(lifo)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> UnindexedProducer for JobQueue<'a, T>
|
||||||
|
where
|
||||||
|
T: Send + Sync + 'a,
|
||||||
|
{
|
||||||
|
type Item = (JobQueueHandle<'a, T>, T);
|
||||||
|
|
||||||
|
fn split(self) -> (Self, Option<Self>) {
|
||||||
|
//eprintln!("{:?}: split start", std::thread::current().id());
|
||||||
|
let len = {
|
||||||
|
let q = self.0.lifo.read().unwrap();
|
||||||
|
q.len()
|
||||||
|
};
|
||||||
|
if len >= 2 {
|
||||||
|
let new_q = {
|
||||||
|
let mut q = self.0.lifo.write().unwrap();
|
||||||
|
let split_off = q.split_off(len / 2);
|
||||||
|
/*eprintln!(
|
||||||
|
"{:?}: split len {}",
|
||||||
|
std::thread::current().id(),
|
||||||
|
split_off.len()
|
||||||
|
);*/
|
||||||
|
JobQueue::new(split_off)
|
||||||
|
};
|
||||||
|
//eprintln!("{:?}: split end", std::thread::current().id());
|
||||||
|
(self, Some(new_q))
|
||||||
|
} else {
|
||||||
|
//eprintln!("{:?}: split none", std::thread::current().id());
|
||||||
|
(self, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fold_with<F>(self, folder: F) -> F
|
||||||
|
where
|
||||||
|
F: Folder<Self::Item>,
|
||||||
|
{
|
||||||
|
let mut folder = folder;
|
||||||
|
let mut count: usize;
|
||||||
|
loop {
|
||||||
|
//eprintln!("{:?}: l1", std::thread::current().id());
|
||||||
|
let (handle, ret) = {
|
||||||
|
let mut q = self.0.lifo.write().unwrap();
|
||||||
|
count = self.0.handle_count.fetch_add(1, Ordering::Relaxed);
|
||||||
|
(JobQueueHandle(Some(self.0.clone())), q.pop())
|
||||||
|
};
|
||||||
|
//eprintln!("{:?}: l2", std::thread::current().id());
|
||||||
|
|
||||||
|
if let Some(v) = ret {
|
||||||
|
//eprintln!("{:?}: consume", std::thread::current().id());
|
||||||
|
folder = folder.consume((handle, v));
|
||||||
|
|
||||||
|
if folder.full() {
|
||||||
|
//eprintln!("{:?}: full", std::thread::current().id());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if count != 0 {
|
||||||
|
//eprintln!("{:?}: Waiting for handles", std::thread::current().id());
|
||||||
|
std::thread::sleep(Duration::from_millis((1) as _));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
//eprintln!("{:?}: count==0", std::thread::current().id());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
folder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> rayon::iter::ParallelIterator for JobQueue<'a, T>
|
||||||
|
where
|
||||||
|
T: Send + Sync + 'a,
|
||||||
|
{
|
||||||
|
type Item = (JobQueueHandle<'a, T>, T);
|
||||||
|
|
||||||
|
fn drive_unindexed<C>(self, consumer: C) -> <C as Consumer<Self::Item>>::Result
|
||||||
|
where
|
||||||
|
C: UnindexedConsumer<Self::Item>,
|
||||||
|
{
|
||||||
|
bridge_unindexed(self, consumer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::JobQueue;
|
||||||
|
const SLEEP_MS: u64 = 10;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn jobqueue_iter_test() {
|
||||||
|
use rayon::iter::IntoParallelIterator as _;
|
||||||
|
use rayon::iter::ParallelIterator as _;
|
||||||
|
use std::time::Duration;
|
||||||
|
/*
|
||||||
|
rayon::ThreadPoolBuilder::new()
|
||||||
|
.num_threads(100)
|
||||||
|
.build_global()
|
||||||
|
.unwrap();
|
||||||
|
*/
|
||||||
|
eprintln!();
|
||||||
|
let mut q = Vec::<usize>::new();
|
||||||
|
q.push(1);
|
||||||
|
q.push(2);
|
||||||
|
q.push(3);
|
||||||
|
q.push(4);
|
||||||
|
q.push(5);
|
||||||
|
q.push(6);
|
||||||
|
q.push(7);
|
||||||
|
q.push(8);
|
||||||
|
let jq = JobQueue::new(q);
|
||||||
|
let now = std::time::Instant::now();
|
||||||
|
|
||||||
|
let mut res = jq
|
||||||
|
.into_par_iter()
|
||||||
|
.map(|(h, v)| {
|
||||||
|
if v % 2 == 0 {
|
||||||
|
h.push(5);
|
||||||
|
}
|
||||||
|
if v % 3 == 0 {
|
||||||
|
h.push(5);
|
||||||
|
}
|
||||||
|
if v % 4 == 0 {
|
||||||
|
h.push(5);
|
||||||
|
}
|
||||||
|
eprintln!("{}", v);
|
||||||
|
std::thread::sleep(Duration::from_millis(SLEEP_MS));
|
||||||
|
v
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
eprintln!("jobqueue: elapsed = {:#?}", now.elapsed());
|
||||||
|
res.sort();
|
||||||
|
assert_eq!(res, vec![1, 2, 3, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 7, 8]);
|
||||||
|
eprintln!(
|
||||||
|
"jobqueue: instead of = {}ms",
|
||||||
|
res.iter().len() * SLEEP_MS as usize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn par_iter_test() {
|
||||||
|
use rayon::iter::IntoParallelIterator as _;
|
||||||
|
use rayon::iter::ParallelIterator as _;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
let now = std::time::Instant::now();
|
||||||
|
|
||||||
|
let res = vec![1, 2, 3, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 6, 7, 8]
|
||||||
|
.into_par_iter()
|
||||||
|
.map(|v| {
|
||||||
|
eprintln!("{}", v);
|
||||||
|
std::thread::sleep(Duration::from_millis(SLEEP_MS));
|
||||||
|
v
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
eprintln!("par_iter: elapsed = {:#?}", now.elapsed());
|
||||||
|
eprintln!("par_iter: instead of = {}ms", res.iter().len() * 10);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue