Skip to content

Commit 660e4af

Browse files
committed
defer drop
1 parent 2eb2249 commit 660e4af

File tree

2 files changed

+79
-8
lines changed

2 files changed

+79
-8
lines changed

src/bench.rs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub(crate) struct NamedBench<'a, I, O> {
2525
pub fun: CallBench<'a, I, O>,
2626
pub num_group_iter: usize,
2727
}
28-
impl<'a, I, O> NamedBench<'a, I, O> {
28+
impl<'a, I, O: OutputValue> NamedBench<'a, I, O> {
2929
pub fn new(bench_id: BenchId, fun: CallBench<'a, I, O>, num_group_iter: usize) -> Self {
3030
Self {
3131
bench_id,
@@ -174,7 +174,7 @@ impl<O> RunResult<O> {
174174
}
175175
}
176176

177-
impl<'a, I, O> NamedBench<'a, I, O> {
177+
impl<'a, I, O: OutputValue> NamedBench<'a, I, O> {
178178
#[inline]
179179
/// Each group has its own number of iterations. This is not the final num_iter
180180
pub fn sample_and_get_iter(&mut self, input: &'a I) -> usize {
@@ -216,14 +216,27 @@ impl<'a, I, O> NamedBench<'a, I, O> {
216216
plugins.emit(PluginEvents::BenchStart {
217217
bench_id: &self.bench_id,
218218
});
219+
debug_assert!(num_iter > 0);
219220
let start = std::time::Instant::now();
220-
let mut res: Option<O> = None;
221-
for _ in 0..num_iter {
222-
res = Some(black_box((self.fun)(input)));
223-
}
224-
let elapsed = start.elapsed();
225221

226-
let run_result = RunResult::new(elapsed.as_nanos() as u64 / num_iter as u64, res.unwrap());
222+
// Defer dropping outputs so destructor cost is not part of the measured time.
223+
let run_result = if O::defer_drop() {
224+
let mut outputs: Vec<O> = Vec::with_capacity(num_iter);
225+
for _ in 0..num_iter {
226+
outputs.push(black_box((self.fun)(input)));
227+
}
228+
let duration_ns = start.elapsed().as_nanos() as u64 / num_iter as u64;
229+
let last_output = outputs.pop().expect("num_iter > 0");
230+
RunResult::new(duration_ns, last_output)
231+
} else {
232+
let mut res: Option<O> = None;
233+
for _ in 0..num_iter {
234+
res = Some(black_box((self.fun)(input)));
235+
}
236+
let duration_ns = start.elapsed().as_nanos() as u64 / num_iter as u64;
237+
RunResult::new(duration_ns, res.unwrap())
238+
};
239+
227240
plugins.emit(PluginEvents::BenchStop {
228241
bench_id: &self.bench_id,
229242
duration: run_result.duration_ns,

src/output_value.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use crate::report::format::{format_duration, format_with_underscores};
2+
use core::mem::{needs_drop, size_of};
3+
use std::collections::HashMap;
24

35
/// Every bench returns an OutputValue, which can be formatted to a string.
46
///
@@ -21,6 +23,16 @@ pub trait OutputValue {
2123
fn column_title() -> &'static str {
2224
"Output"
2325
}
26+
27+
/// Whether the output should be buffered and dropped after the measurement was recorded.
28+
/// Defaults to deferring drop for types that actually need it and are not zero-sized.
29+
#[inline]
30+
fn defer_drop() -> bool
31+
where
32+
Self: Sized,
33+
{
34+
needs_drop::<Self>() && size_of::<Self>() > 0
35+
}
2436
}
2537

2638
impl OutputValue for () {
@@ -74,13 +86,59 @@ impl OutputValue for std::time::Instant {
7486
}
7587
}
7688

89+
impl<T> OutputValue for Vec<T> {
90+
fn format(&self) -> Option<String> {
91+
Some(format_with_underscores(self.len() as u64))
92+
}
93+
fn column_title() -> &'static str {
94+
"Vec(len)"
95+
}
96+
}
97+
impl<K, V> OutputValue for HashMap<K, V> {
98+
fn format(&self) -> Option<String> {
99+
Some(format_with_underscores(self.len() as u64))
100+
}
101+
fn column_title() -> &'static str {
102+
"Map(len)"
103+
}
104+
}
105+
77106
#[cfg(test)]
78107
mod tests {
79108
use super::*;
80109

110+
struct NeedsDrop(u8);
111+
impl Drop for NeedsDrop {
112+
fn drop(&mut self) {}
113+
}
114+
impl OutputValue for NeedsDrop {
115+
fn format(&self) -> Option<String> {
116+
Some(self.0.to_string())
117+
}
118+
}
119+
81120
#[test]
82121
fn format_u64_test() {
83122
let value = 123456789u64;
84123
assert_eq!(value.format(), Some("123_456_789".to_string()));
85124
}
125+
126+
#[test]
127+
fn should_buffer_outputs_respects_drop_semantics() {
128+
assert!(NeedsDrop::defer_drop());
129+
assert!(!bool::defer_drop());
130+
131+
struct ZeroSizedNeedsDrop;
132+
impl Drop for ZeroSizedNeedsDrop {
133+
fn drop(&mut self) {}
134+
}
135+
impl OutputValue for ZeroSizedNeedsDrop {
136+
fn format(&self) -> Option<String> {
137+
None
138+
}
139+
}
140+
141+
// Zero-sized types should not allocate buffering slots
142+
assert!(!ZeroSizedNeedsDrop::defer_drop());
143+
}
86144
}

0 commit comments

Comments
 (0)