Skip to content

Commit a232ca4

Browse files
authored
Merge pull request #116 from KSXGitHub/use-atomics-to-improve-performance
Use atomics to improve performance
2 parents e036d23 + 97c702d commit a232ca4

File tree

7 files changed

+81
-33
lines changed

7 files changed

+81
-33
lines changed

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "parallel-disk-usage"
33
description = "Highly parallelized, blazing fast directory tree analyzer"
44
version = "0.6.1"
55
authors = ["khai96_ <[email protected]>"]
6-
edition = "2018"
6+
edition = "2021"
77
readme = "README.md"
88
license = "Apache-2.0"
99
documentation = "https://docs.rs/parallel-disk-usage"

rust-toolchain

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.54.0
1+
1.58.1

src/app.rs

+1
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ impl App {
110110
where
111111
Data: Size + Into<u64> + Send + Sync,
112112
ProgressReport<Data>: Default + 'static,
113+
u64: Into<Data>,
113114
{
114115
ProgressAndErrorReporter::new(
115116
ProgressReport::TEXT,
+28-30
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use super::{ErrorReport, Event, ParallelReporter, ProgressReport, Reporter, Size};
2-
use pipe_trait::Pipe;
2+
use progress_report_state::ProgressReportState;
33
use std::{
44
any::Any,
5-
sync::{Arc, RwLock},
5+
marker::PhantomData,
6+
ops::ControlFlow,
7+
sync::{atomic::Ordering::Relaxed, Arc},
68
thread::{sleep, spawn, JoinHandle},
79
time::Duration,
810
};
@@ -15,19 +17,23 @@ pub struct ProgressAndErrorReporter<Data, ReportError>
1517
where
1618
Data: Size + Send + Sync,
1719
ReportError: Fn(ErrorReport) + Sync,
20+
u64: Into<Data>,
1821
{
1922
/// Progress information.
20-
progress: Arc<RwLock<Option<ProgressReport<Data>>>>,
23+
progress: Arc<ProgressReportState>,
2124
/// Report encountered error.
2225
report_error: ReportError,
2326
/// Join handle of progress reporting thread.
2427
progress_reporter_handle: JoinHandle<()>,
28+
/// Keep generic parameters.
29+
_phantom: PhantomData<Data>,
2530
}
2631

2732
impl<Data, ReportError> ProgressAndErrorReporter<Data, ReportError>
2833
where
2934
Data: Size + Send + Sync,
3035
ReportError: Fn(ErrorReport) + Sync,
36+
u64: Into<Data>,
3137
{
3238
/// Create a new [`ProgressAndErrorReporter`] from a report function.
3339
pub fn new<ReportProgress>(
@@ -39,41 +45,36 @@ where
3945
ProgressReport<Data>: Default + 'static,
4046
ReportProgress: Fn(ProgressReport<Data>) + Send + Sync + 'static,
4147
{
42-
let progress = ProgressReport::default()
43-
.pipe(Some)
44-
.pipe(RwLock::new)
45-
.pipe(Arc::new);
48+
let progress = Arc::new(ProgressReportState::default());
4649
let progress_thread = progress.clone();
4750
let progress_reporter_handle = spawn(move || loop {
4851
sleep(progress_report_interval);
49-
if let Ok(progress) = progress_thread.read().as_deref() {
50-
if let Some(progress) = *progress {
51-
report_progress(progress);
52-
} else {
53-
break;
54-
}
55-
}
52+
match progress_thread.to_progress_report() {
53+
ControlFlow::Continue(progress) => report_progress(progress),
54+
ControlFlow::Break(()) => break,
55+
};
5656
});
5757
ProgressAndErrorReporter {
5858
progress,
5959
report_error,
6060
progress_reporter_handle,
61+
_phantom: PhantomData,
6162
}
6263
}
6364

6465
/// Stop the thread that reports progress.
6566
///
6667
/// This function would be automatically invoked once the value is [dropped](Drop).
6768
pub fn stop_progress_reporter(&self) {
68-
let mut progress = self.progress.write().expect("lock progress to stop");
69-
*progress = None;
69+
self.progress.stopped.store(true, Relaxed);
7070
}
7171
}
7272

7373
impl<Data, ReportError> Reporter<Data> for ProgressAndErrorReporter<Data, ReportError>
7474
where
75-
Data: Size + Send + Sync,
75+
Data: Size + Into<u64> + Send + Sync,
7676
ReportError: Fn(ErrorReport) + Sync,
77+
u64: Into<Data>,
7778
{
7879
fn report(&self, event: Event<Data>) {
7980
use Event::*;
@@ -82,38 +83,35 @@ where
8283
report_error,
8384
..
8485
} = self;
85-
macro_rules! handle_field {
86-
($($field:ident $operator:tt $addend:expr;)+) => {
87-
if let Some(progress) = progress.write().ok().as_mut().and_then(|x| x.as_mut()) {
88-
$(progress.$field $operator $addend;)+
89-
}
86+
macro_rules! bump {
87+
($field:ident += $delta:expr) => {
88+
progress.$field.fetch_add($delta, Relaxed)
9089
};
9190
}
9291
match event {
9392
ReceiveData(data) => {
94-
handle_field! {
95-
items += 1;
96-
total += data;
97-
}
93+
bump!(items += 1);
94+
bump!(total += data.into());
9895
}
9996
EncounterError(error_report) => {
10097
report_error(error_report);
101-
handle_field! {
102-
errors += 1;
103-
}
98+
bump!(errors += 1);
10499
}
105100
}
106101
}
107102
}
108103

109104
impl<Data, ReportError> ParallelReporter<Data> for ProgressAndErrorReporter<Data, ReportError>
110105
where
111-
Data: Size + Send + Sync,
106+
Data: Size + Into<u64> + Send + Sync,
112107
ReportError: Fn(ErrorReport) + Sync,
108+
u64: Into<Data>,
113109
{
114110
type DestructionError = Box<dyn Any + Send + 'static>;
115111
fn destroy(self) -> Result<(), Self::DestructionError> {
116112
self.stop_progress_reporter();
117113
self.progress_reporter_handle.join()
118114
}
119115
}
116+
117+
mod progress_report_state;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use crate::{reporter::ProgressReport, size::Size};
2+
use std::{
3+
ops::ControlFlow,
4+
sync::atomic::{AtomicBool, AtomicU64, Ordering::Relaxed},
5+
};
6+
7+
/// Like [`ProgressReport`] but mutable.
8+
#[derive(Debug, Default)]
9+
pub struct ProgressReportState {
10+
/// Whether the progress has stopped.
11+
pub stopped: AtomicBool,
12+
/// Number of scanned items.
13+
pub items: AtomicU64,
14+
/// Total size of scanned items.
15+
pub total: AtomicU64,
16+
/// Number of occurred errors.
17+
pub errors: AtomicU64,
18+
}
19+
20+
impl ProgressReportState {
21+
/// Yield [`ProgressReport`] if it is running.
22+
/// Return `Break` otherwise.
23+
pub fn to_progress_report<Data>(&self) -> ControlFlow<(), ProgressReport<Data>>
24+
where
25+
Data: Size,
26+
u64: Into<Data>,
27+
{
28+
macro_rules! load {
29+
($field:ident) => {
30+
self.$field.load(Relaxed)
31+
};
32+
}
33+
34+
if load!(stopped) {
35+
return ControlFlow::Break(());
36+
}
37+
38+
let items = load!(items);
39+
let total = load!(total).into();
40+
let errors = load!(errors);
41+
ControlFlow::Continue(ProgressReport {
42+
items,
43+
total,
44+
errors,
45+
})
46+
}
47+
}

src/visualizer/proportion_bar.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ impl Display for ProportionBarDisplay {
7676
level2 = bar.display_level2(),
7777
level3 = bar.display_level3(),
7878
level4 = bar.display_level4(),
79-
);
79+
)
8080
};
8181
}
8282
match align {

tests/visualizer.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![recursion_limit = "256"]
2+
13
use parallel_disk_usage::{
24
bytes_format::BytesFormat::*,
35
data_tree::DataTree,

0 commit comments

Comments
 (0)