diff options
| author | bors <bors@rust-lang.org> | 2018-06-27 04:02:05 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2018-06-27 04:02:05 +0000 |
| commit | 612c28004cba9e8e7bcd7e2a9dcdf2c2736f0e81 (patch) | |
| tree | 3ee5ea9aaf650a3e31fd6c52859bae1460f10d7e /src | |
| parent | d6e2239a0718b230444f7f218add58db5732817a (diff) | |
| parent | d22ad76ca83acda1428829173451eee0221f685a (diff) | |
| download | rust-612c28004cba9e8e7bcd7e2a9dcdf2c2736f0e81.tar.gz rust-612c28004cba9e8e7bcd7e2a9dcdf2c2736f0e81.zip | |
Auto merge of #51598 - Pazzaz:master, r=sfackler
Optimize sum of Durations by using custom function
The current `impl Sum for Duration` uses `fold` to perform several `add`s (or really `checked_add`s) of durations. In doing so, it has to guarantee the number of nanoseconds is valid after every addition. If you squeese the current implementation into a single function it looks kind of like this:
````rust
fn sum<I: Iterator<Item = Duration>>(iter: I) -> Duration {
let mut sum = Duration::new(0, 0);
for rhs in iter {
if let Some(mut secs) = sum.secs.checked_add(rhs.secs) {
let mut nanos = sum.nanos + rhs.nanos;
if nanos >= NANOS_PER_SEC {
nanos -= NANOS_PER_SEC;
if let Some(new_secs) = secs.checked_add(1) {
secs = new_secs;
} else {
panic!("overflow when adding durations");
}
}
sum = Duration { secs, nanos }
} else {
panic!("overflow when adding durations");
}
}
sum
}
````
We only need to check if `nanos` is in the correct range when giving our final answer so we can have a more optimized version like so:
````rust
fn sum<I: Iterator<Item = Duration>>(iter: I) -> Duration {
let mut total_secs: u64 = 0;
let mut total_nanos: u64 = 0;
for entry in iter {
total_secs = total_secs
.checked_add(entry.secs)
.expect("overflow in iter::sum over durations");
total_nanos = match total_nanos.checked_add(entry.nanos as u64) {
Some(n) => n,
None => {
total_secs = total_secs
.checked_add(total_nanos / NANOS_PER_SEC as u64)
.expect("overflow in iter::sum over durations");
(total_nanos % NANOS_PER_SEC as u64) + entry.nanos as u64
}
};
}
total_secs = total_secs
.checked_add(total_nanos / NANOS_PER_SEC as u64)
.expect("overflow in iter::sum over durations");
total_nanos = total_nanos % NANOS_PER_SEC as u64;
Duration {
secs: total_secs,
nanos: total_nanos as u32,
}
}
````
We now only convert `total_nanos` to `total_secs` (1) if `total_nanos` overflows and (2) at the end of the function when we have to output a valid `Duration`. This gave a 5-22% performance improvement when I benchmarked it, depending on how big the `nano` value of the `Duration`s in `iter` were.
Diffstat (limited to 'src')
| -rw-r--r-- | src/libcore/tests/time.rs | 14 | ||||
| -rw-r--r-- | src/libcore/time.rs | 34 |
2 files changed, 46 insertions, 2 deletions
diff --git a/src/libcore/tests/time.rs b/src/libcore/tests/time.rs index df139965753..466f28f0ef0 100644 --- a/src/libcore/tests/time.rs +++ b/src/libcore/tests/time.rs @@ -162,6 +162,20 @@ fn checked_div() { } #[test] +fn correct_sum() { + let durations = [ + Duration::new(1, 999_999_999), + Duration::new(2, 999_999_999), + Duration::new(0, 999_999_999), + Duration::new(0, 999_999_999), + Duration::new(0, 999_999_999), + Duration::new(5, 0), + ]; + let sum = durations.iter().sum::<Duration>(); + assert_eq!(sum, Duration::new(1+2+5+4, 1_000_000_000 - 5)); +} + +#[test] fn debug_formatting_extreme_values() { assert_eq!( format!("{:?}", Duration::new(18_446_744_073_709_551_615, 123_456_789)), diff --git a/src/libcore/time.rs b/src/libcore/time.rs index 563eea0066d..25721b7fcec 100644 --- a/src/libcore/time.rs +++ b/src/libcore/time.rs @@ -524,17 +524,47 @@ impl DivAssign<u32> for Duration { } } +macro_rules! sum_durations { + ($iter:expr) => {{ + let mut total_secs: u64 = 0; + let mut total_nanos: u64 = 0; + + for entry in $iter { + total_secs = total_secs + .checked_add(entry.secs) + .expect("overflow in iter::sum over durations"); + total_nanos = match total_nanos.checked_add(entry.nanos as u64) { + Some(n) => n, + None => { + total_secs = total_secs + .checked_add(total_nanos / NANOS_PER_SEC as u64) + .expect("overflow in iter::sum over durations"); + (total_nanos % NANOS_PER_SEC as u64) + entry.nanos as u64 + } + }; + } + total_secs = total_secs + .checked_add(total_nanos / NANOS_PER_SEC as u64) + .expect("overflow in iter::sum over durations"); + total_nanos = total_nanos % NANOS_PER_SEC as u64; + Duration { + secs: total_secs, + nanos: total_nanos as u32, + } + }}; +} + #[stable(feature = "duration_sum", since = "1.16.0")] impl Sum for Duration { fn sum<I: Iterator<Item=Duration>>(iter: I) -> Duration { - iter.fold(Duration::new(0, 0), |a, b| a + b) + sum_durations!(iter) } } #[stable(feature = "duration_sum", since = "1.16.0")] impl<'a> Sum<&'a Duration> for Duration { fn sum<I: Iterator<Item=&'a Duration>>(iter: I) -> Duration { - iter.fold(Duration::new(0, 0), |a, b| a + *b) + sum_durations!(iter) } } |
