diff --git a/benches/distributions.rs b/benches/distributions.rs index d5ead90d5b1..1da0f1466c9 100644 --- a/benches/distributions.rs +++ b/benches/distributions.rs @@ -9,6 +9,7 @@ const RAND_BENCH_N: u64 = 1000; use std::mem::size_of; use test::Bencher; +use std::time::Duration; use rand::{Rng, FromEntropy}; use rand::rngs::SmallRng; @@ -54,6 +55,26 @@ macro_rules! distr_float { } } +macro_rules! distr_duration { + ($fnn:ident, $distr:expr) => { + #[bench] + fn $fnn(b: &mut Bencher) { + let mut rng = SmallRng::from_entropy(); + let distr = $distr; + + b.iter(|| { + let mut accum = Duration::new(0, 0); + for _ in 0..::RAND_BENCH_N { + let x: Duration = distr.sample(&mut rng); + accum = accum.checked_add(x).unwrap_or(Duration::new(u64::max_value(), 999_999_999)); + } + accum + }); + b.bytes = size_of::() as u64 * ::RAND_BENCH_N; + } + } +} + macro_rules! distr { ($fnn:ident, $ty:ty, $distr:expr) => { #[bench] @@ -85,6 +106,25 @@ distr_int!(distr_uniform_i128, i128, Uniform::new(-123_456_789_123i128, 123_456_ distr_float!(distr_uniform_f32, f32, Uniform::new(2.26f32, 2.319)); distr_float!(distr_uniform_f64, f64, Uniform::new(2.26f64, 2.319)); +const LARGE_SEC: u64 = u64::max_value() / 1000; + +distr_duration!(distr_uniform_duration_largest, + Uniform::new_inclusive(Duration::new(0, 0), Duration::new(u64::max_value(), 999_999_999)) +); +distr_duration!(distr_uniform_duration_large, + Uniform::new(Duration::new(0, 0), Duration::new(LARGE_SEC, 1_000_000_000 / 2)) +); +distr_duration!(distr_uniform_duration_one, + Uniform::new(Duration::new(0, 0), Duration::new(1, 0)) +); +distr_duration!(distr_uniform_duration_variety, + Uniform::new(Duration::new(10000, 423423), Duration::new(200000, 6969954)) +); +distr_duration!(distr_uniform_duration_edge, + Uniform::new_inclusive(Duration::new(LARGE_SEC, 999_999_999), Duration::new(LARGE_SEC + 1, 1)) +); + + // standard distr_int!(distr_standard_i8, i8, Standard); distr_int!(distr_standard_i16, i16, Standard); diff --git a/src/distributions/uniform.rs b/src/distributions/uniform.rs index 9a0acd018ac..0cffbffa628 100644 --- a/src/distributions/uniform.rs +++ b/src/distributions/uniform.rs @@ -693,18 +693,23 @@ uniform_float_impl! { f64x8, u64x8, f64, u64, 64 - 52 } #[cfg(feature = "std")] #[derive(Clone, Copy, Debug)] pub struct UniformDuration { - offset: Duration, mode: UniformDurationMode, + offset: u32, } #[cfg(feature = "std")] #[derive(Debug, Copy, Clone)] enum UniformDurationMode { Small { + secs: u64, + nanos: Uniform, + }, + Medium { nanos: Uniform, }, Large { - size: Duration, + max_secs: u64, + max_nanos: u32, secs: Uniform, } } @@ -737,52 +742,72 @@ impl UniformSampler for UniformDuration { let low = *low_b.borrow(); let high = *high_b.borrow(); assert!(low <= high, "Uniform::new_inclusive called with `low > high`"); - let size = high - low; - let nanos = size - .as_secs() - .checked_mul(1_000_000_000) - .and_then(|n| n.checked_add(size.subsec_nanos() as u64)); - - let mode = match nanos { - Some(nanos) => { - UniformDurationMode::Small { - nanos: Uniform::new_inclusive(0, nanos), - } + + let low_s = low.as_secs(); + let low_n = low.subsec_nanos(); + let mut high_s = high.as_secs(); + let mut high_n = high.subsec_nanos(); + + if high_n < low_n { + high_s = high_s - 1; + high_n = high_n + 1_000_000_000; + } + + let mode = if low_s == high_s { + UniformDurationMode::Small { + secs: low_s, + nanos: Uniform::new_inclusive(low_n, high_n), } - None => { + } else { + let max = high_s + .checked_mul(1_000_000_000) + .and_then(|n| n.checked_add(high_n as u64)); + + if let Some(higher_bound) = max { + let lower_bound = low_s * 1_000_000_000 + low_n as u64; + UniformDurationMode::Medium { + nanos: Uniform::new_inclusive(lower_bound, higher_bound), + } + } else { + // An offset is applied to simplify generation of nanoseconds + let max_nanos = high_n - low_n; UniformDurationMode::Large { - size: size, - secs: Uniform::new_inclusive(0, size.as_secs()), + max_secs: high_s, + max_nanos, + secs: Uniform::new_inclusive(low_s, high_s), } } }; - UniformDuration { mode, - offset: low, + offset: low_n, } } #[inline] fn sample(&self, rng: &mut R) -> Duration { - let d = match self.mode { - UniformDurationMode::Small { nanos } => { + match self.mode { + UniformDurationMode::Small { secs, nanos } => { + let n = nanos.sample(rng); + Duration::new(secs, n) + } + UniformDurationMode::Medium { nanos } => { let nanos = nanos.sample(rng); Duration::new(nanos / 1_000_000_000, (nanos % 1_000_000_000) as u32) } - UniformDurationMode::Large { size, secs } => { + UniformDurationMode::Large { max_secs, max_nanos, secs } => { // constant folding means this is at least as fast as `gen_range` let nano_range = Uniform::new(0, 1_000_000_000); loop { - let d = Duration::new(secs.sample(rng), nano_range.sample(rng)); - if d <= size { - break d; + let s = secs.sample(rng); + let n = nano_range.sample(rng); + if !(s == max_secs && n > max_nanos) { + let sum = n + self.offset; + break Duration::new(s, sum); } } } - }; - - self.offset + d + } } }