diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f7cd1da0..ccb72ed4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/benches/benchmarks/custom_measurement.rs b/benches/benchmarks/custom_measurement.rs index 449f9030c..306bd4faa 100644 --- a/benches/benchmarks/custom_measurement.rs +++ b/benches/benchmarks/custom_measurement.rs @@ -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)) + ), } } @@ -53,6 +61,9 @@ impl ValueFormatter for HalfSecFormatter { "elem/s/2" } + Throughput::ElementsAndBytes { elements, bytes: _ } => { + self.scale_throughputs(_typical, &Throughput::Elements(elements), values) + } } } diff --git a/benches/benchmarks/with_inputs.rs b/benches/benchmarks/with_inputs.rs index b0b12a89f..a8910e882 100644 --- a/benches/benchmarks/with_inputs.rs +++ b/benches/benchmarks/with_inputs.rs @@ -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::>()); + }); + } + group.finish(); } criterion_group!(benches, from_elem); diff --git a/book/src/user_guide/advanced_configuration.md b/book/src/user_guide/advanced_configuration.md index d7be9bca6..59e983c1f 100644 --- a/book/src/user_guide/advanced_configuration.md +++ b/book/src/user_guide/advanced_configuration.md @@ -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. diff --git a/src/lib.rs b/src/lib.rs index f349d6484..4aafaab81 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 diff --git a/src/measurement.rs b/src/measurement.rs index 63719753d..393fe4ab7 100644 --- a/src/measurement.rs +++ b/src/measurement.rs @@ -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) + } } } diff --git a/src/report.rs b/src/report.rs index c5448fdbb..18aae058c 100644 --- a/src/report.rs +++ b/src/report.rs @@ -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() @@ -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() @@ -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), @@ -790,6 +795,18 @@ fn compare_to_threshold(estimate: &Estimate, noise: f64) -> ComparisonResult { } } +fn measurement_throughputs(mes: &MeasurementData<'_>) -> Vec { + 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::*;