Skip to content

Commit

Permalink
Merge pull request #583 from Pazzaz/master
Browse files Browse the repository at this point in the history
Speed up Uniform Duration sampling.
  • Loading branch information
dhardy committed Aug 23, 2018
2 parents 22d6607 + 92ff078 commit ea467e7
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 27 deletions.
40 changes: 40 additions & 0 deletions benches/distributions.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -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::<Duration>() as u64 * ::RAND_BENCH_N;
}
}
}

macro_rules! distr {
($fnn:ident, $ty:ty, $distr:expr) => {
#[bench]
Expand Down Expand Up @@ -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);
Expand Down
79 changes: 52 additions & 27 deletions src/distributions/uniform.rs
Expand Up @@ -837,18 +837,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<u32>,
},
Medium {
nanos: Uniform<u64>,
},
Large {
size: Duration,
max_secs: u64,
max_nanos: u32,
secs: Uniform<u64>,
}
}
Expand Down Expand Up @@ -881,52 +886,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<R: Rng + ?Sized>(&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
}
}
}

Expand Down

0 comments on commit ea467e7

Please sign in to comment.