Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for reporting throughput and elements simultaneously. #722

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

### Fixed
- Quick mode (--quick) no longer crashes with measured times over 5 seconds when --noplot is not active
- `Throughput::ElementsAndBytes` added to allow the text summary to report throughput in both units simultaneously.

## [0.5.0] - 2023-05-23

Expand Down
11 changes: 11 additions & 0 deletions benches/benchmarks/custom_measurement.rs
Expand Up @@ -21,6 +21,14 @@ impl ValueFormatter for HalfSecFormatter {
"{} elem/s/2",
(elems as f64) / (value * 2f64 * 10f64.powi(-9))
),
Throughput::ElementsAndBytes {
elements: _,
bytes: _,
} => format!(
"{} elem/s/2, {} b/s/2",
(elems as f64) / (value * 2f64 * 10f64.powi(-9),),
(bytes as f64) / (value * 2f64 * 10f64.powi(-9))
),
}
}

Expand Down Expand Up @@ -53,6 +61,9 @@ impl ValueFormatter for HalfSecFormatter {

"elem/s/2"
}
Throughput::ElementsAndBytes { elements, bytes: _ } => {
self.scale_throughputs(_typical, &Throughput::Elements(elements), values)
}
}
}

Expand Down
12 changes: 12 additions & 0 deletions benches/benchmarks/with_inputs.rs
Expand Up @@ -22,6 +22,18 @@ fn from_elem(c: &mut Criterion) {
});
}
group.finish();

let mut group = c.benchmark_group("from_elem_dual_throughput");
for size in [KB, 2 * KB].iter() {
group.throughput(Throughput::ElementsAndBytes {
elements: *size as u64,
bytes: 2 * *size as u64,
});
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, &size| {
b.iter(|| iter::repeat(0u16).take(size).collect::<Vec<_>>());
});
}
group.finish();
}

criterion_group!(benches, from_elem);
3 changes: 3 additions & 0 deletions book/src/user_guide/advanced_configuration.md
Expand Up @@ -119,6 +119,9 @@ alloc time: [5.9846 ms 6.0192 ms 6.0623 ms]
thrpt: [164.95 MiB/s 166.14 MiB/s 167.10 MiB/s]
```

You can also request that the throughput measure both elements/s and bytes/s if, for example, you're doing some kind of data processing and
want to understand both the number of records/s and the total bytes/s achieved. This is achieved by setting `throughput` to `Throughput::ElementsAndBytes`.

## Chart Axis Scaling

By default, Criterion.rs generates plots using a linear-scale axis. When using parameterized benchmarks, it is common for the input sizes to scale exponentially in order to cover a wide range of possible inputs. In this situation, it may be easier to read the resulting plots with a logarithmic axis.
Expand Down
17 changes: 17 additions & 0 deletions src/lib.rs
Expand Up @@ -1293,6 +1293,23 @@ pub enum Throughput {
/// collection, but could also be the number of lines of input text or the number of values to
/// parse.
Elements(u64),

/// Measure throughput in terms of both elements/second and bytes/second. Typically,
/// this would be used if you have a collection of rows where `elements` would be the length of
/// the collection and `bytes` would be the total size of the collection if you're processing
/// the entire collection in one iteration. This will make sure the report simultaneously
/// includes on the rows/s and MB/s that data processing is able to achieve.
/// The elements are considered the "primary" throughput being reported on (i.e. what will appear
/// in the BenchmarkId).
ElementsAndBytes {
/// The value should be the number of elements processed by one iteration of the benchmarked code.
/// Typically, this would be the size of a collection, but could also be the number of lines of
/// input text or the number of values to parse.
elements: u64,
/// The value should be the number of bytes processed by one iteration of the benchmarked code.
/// Typically, this would be the length of an input string or `&[u8]`.
bytes: u64,
},
}

/// Axis scaling type
Expand Down
4 changes: 4 additions & 0 deletions src/measurement.rs
Expand Up @@ -182,6 +182,10 @@ impl ValueFormatter for DurationFormatter {
self.bytes_per_second_decimal(bytes as f64, typical, values)
}
Throughput::Elements(elems) => self.elements_per_second(elems as f64, typical, values),
// The caller should be formatting the bytes and elements separately.
Throughput::ElementsAndBytes { elements, bytes: _ } => {
self.elements_per_second(elements as f64, typical, values)
}
}
}

Expand Down
19 changes: 18 additions & 1 deletion src/report.rs
Expand Up @@ -176,6 +176,7 @@ impl BenchmarkId {
Some(Throughput::Bytes(n))
| Some(Throughput::Elements(n))
| Some(Throughput::BytesDecimal(n)) => Some(n as f64),
Some(Throughput::ElementsAndBytes { elements, bytes: _ }) => Some(elements as f64),
None => self
.value_str
.as_ref()
Expand All @@ -188,6 +189,10 @@ impl BenchmarkId {
Some(Throughput::Bytes(_)) => Some(ValueType::Bytes),
Some(Throughput::BytesDecimal(_)) => Some(ValueType::Bytes),
Some(Throughput::Elements(_)) => Some(ValueType::Elements),
Some(Throughput::ElementsAndBytes {
elements: _,
bytes: _,
}) => Some(ValueType::Elements),
None => self
.value_str
.as_ref()
Expand Down Expand Up @@ -577,7 +582,7 @@ impl Report for CliReport {
);
}

if let Some(ref throughput) = meas.throughput {
for ref throughput in measurement_throughputs(meas) {
println!(
"{}thrpt: [{} {} {}]",
" ".repeat(24),
Expand Down Expand Up @@ -790,6 +795,18 @@ fn compare_to_threshold(estimate: &Estimate, noise: f64) -> ComparisonResult {
}
}

fn measurement_throughputs(mes: &MeasurementData<'_>) -> Vec<Throughput> {
mes.throughput
.as_ref()
.map(|t| match t {
Throughput::ElementsAndBytes { elements, bytes } => {
vec![Throughput::Elements(*elements), Throughput::Bytes(*bytes)]
}
_ => vec![t.clone()],
})
.unwrap_or(vec![])
}

#[cfg(test)]
mod test {
use super::*;
Expand Down