Skip to content

Commit 612c280

Browse files
committed
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.
2 parents d6e2239 + d22ad76 commit 612c280

File tree

2 files changed

+46
-2
lines changed

2 files changed

+46
-2
lines changed

src/libcore/tests/time.rs

+14
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,20 @@ fn checked_div() {
161161
assert_eq!(Duration::new(2, 0).checked_div(0), None);
162162
}
163163

164+
#[test]
165+
fn correct_sum() {
166+
let durations = [
167+
Duration::new(1, 999_999_999),
168+
Duration::new(2, 999_999_999),
169+
Duration::new(0, 999_999_999),
170+
Duration::new(0, 999_999_999),
171+
Duration::new(0, 999_999_999),
172+
Duration::new(5, 0),
173+
];
174+
let sum = durations.iter().sum::<Duration>();
175+
assert_eq!(sum, Duration::new(1+2+5+4, 1_000_000_000 - 5));
176+
}
177+
164178
#[test]
165179
fn debug_formatting_extreme_values() {
166180
assert_eq!(

src/libcore/time.rs

+32-2
Original file line numberDiff line numberDiff line change
@@ -524,17 +524,47 @@ impl DivAssign<u32> for Duration {
524524
}
525525
}
526526

527+
macro_rules! sum_durations {
528+
($iter:expr) => {{
529+
let mut total_secs: u64 = 0;
530+
let mut total_nanos: u64 = 0;
531+
532+
for entry in $iter {
533+
total_secs = total_secs
534+
.checked_add(entry.secs)
535+
.expect("overflow in iter::sum over durations");
536+
total_nanos = match total_nanos.checked_add(entry.nanos as u64) {
537+
Some(n) => n,
538+
None => {
539+
total_secs = total_secs
540+
.checked_add(total_nanos / NANOS_PER_SEC as u64)
541+
.expect("overflow in iter::sum over durations");
542+
(total_nanos % NANOS_PER_SEC as u64) + entry.nanos as u64
543+
}
544+
};
545+
}
546+
total_secs = total_secs
547+
.checked_add(total_nanos / NANOS_PER_SEC as u64)
548+
.expect("overflow in iter::sum over durations");
549+
total_nanos = total_nanos % NANOS_PER_SEC as u64;
550+
Duration {
551+
secs: total_secs,
552+
nanos: total_nanos as u32,
553+
}
554+
}};
555+
}
556+
527557
#[stable(feature = "duration_sum", since = "1.16.0")]
528558
impl Sum for Duration {
529559
fn sum<I: Iterator<Item=Duration>>(iter: I) -> Duration {
530-
iter.fold(Duration::new(0, 0), |a, b| a + b)
560+
sum_durations!(iter)
531561
}
532562
}
533563

534564
#[stable(feature = "duration_sum", since = "1.16.0")]
535565
impl<'a> Sum<&'a Duration> for Duration {
536566
fn sum<I: Iterator<Item=&'a Duration>>(iter: I) -> Duration {
537-
iter.fold(Duration::new(0, 0), |a, b| a + *b)
567+
sum_durations!(iter)
538568
}
539569
}
540570

0 commit comments

Comments
 (0)