Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Uniform float improvements #1289

Merged
merged 6 commits into from
May 1, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,9 @@ harness = false
[[bench]]
name = "shuffle"
path = "benches/shuffle.rs"
harness = false
harness = false

[[bench]]
name = "uniform_float"
path = "benches/uniform_float.rs"
harness = false
112 changes: 112 additions & 0 deletions benches/uniform_float.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright 2021 Developers of the Rand project.
dhardy marked this conversation as resolved.
Show resolved Hide resolved
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Implement benchmarks for uniform distributions over FP types
//!
//! Sampling methods compared:
//!
//! - sample: current method: (x12 - 1.0) * (b - a) + a

use core::time::Duration;
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use rand::distributions::uniform::{SampleUniform, Uniform, UniformSampler};
use rand::prelude::*;
use rand_chacha::ChaCha8Rng;
use rand_pcg::{Pcg32, Pcg64};

const WARM_UP_TIME: Duration = Duration::from_millis(1000);
const MEASUREMENT_TIME: Duration = Duration::from_secs(3);
const SAMPLE_SIZE: usize = 100_000;
const N_RESAMPLES: usize = 10_000;

macro_rules! single_random {
($name:literal, $R:ty, $T:ty, $f:ident, $g:expr) => {
$g.bench_function(BenchmarkId::new(stringify!($R), $name), |b| {
let mut rng = <$R>::from_entropy();
let (mut low, mut high);
loop {
low = <$T>::from_bits(rng.gen());
high = <$T>::from_bits(rng.gen());
if (low < high) && (high - low).is_normal() {
break;
}
}

b.iter(|| <$T as SampleUniform>::Sampler::$f(low, high, &mut rng));
});
};

($R:ty, $T:ty, $g:expr) => {
single_random!("sample", $R, $T, sample_single, $g);
single_random!("sample_inclusive", $R, $T, sample_single_inclusive, $g);
};

($c:expr, $T:ty) => {{
let mut g = $c.benchmark_group(concat!("single_random_", stringify!($T)));
g.sample_size(SAMPLE_SIZE);
g.warm_up_time(WARM_UP_TIME);
g.measurement_time(MEASUREMENT_TIME);
g.nresamples(N_RESAMPLES);
single_random!(SmallRng, $T, g);
single_random!(ChaCha8Rng, $T, g);
single_random!(Pcg32, $T, g);
single_random!(Pcg64, $T, g);
vks marked this conversation as resolved.
Show resolved Hide resolved
g.finish();
}};
}

fn single_random(c: &mut Criterion) {
single_random!(c, f32);
single_random!(c, f64);
}

macro_rules! distr_random {
($name:literal, $R:ty, $T:ty, $f:ident, $g:expr) => {
$g.bench_function(BenchmarkId::new(stringify!($R), $name), |b| {
let mut rng = <$R>::from_entropy();
let dist = loop {
let low = <$T>::from_bits(rng.gen());
let high = <$T>::from_bits(rng.gen());
if let Ok(dist) = Uniform::<$T>::new_inclusive(low, high) {
break dist;
}
};

b.iter(|| <$T as SampleUniform>::Sampler::$f(&dist.0, &mut rng));
});
};

($R:ty, $T:ty, $g:expr) => {
distr_random!("sample", $R, $T, sample, $g);
};

($c:expr, $T:ty) => {{
let mut g = $c.benchmark_group(concat!("distr_random_", stringify!($T)));
g.sample_size(SAMPLE_SIZE);
g.warm_up_time(WARM_UP_TIME);
g.measurement_time(MEASUREMENT_TIME);
g.nresamples(N_RESAMPLES);
distr_random!(SmallRng, $T, g);
distr_random!(ChaCha8Rng, $T, g);
distr_random!(Pcg32, $T, g);
distr_random!(Pcg64, $T, g);
vks marked this conversation as resolved.
Show resolved Hide resolved
g.finish();
}};
}

fn distr_random(c: &mut Criterion) {
distr_random!(c, f32);
distr_random!(c, f64);
}

criterion_group! {
name = benches;
config = Criterion::default();
targets = single_random, distr_random
}
criterion_main!(benches);
49 changes: 48 additions & 1 deletion src/distributions/uniform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,8 @@ use serde::{Serialize, Deserialize};
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde1", serde(bound(serialize = "X::Sampler: Serialize")))]
#[cfg_attr(feature = "serde1", serde(bound(deserialize = "X::Sampler: Deserialize<'de>")))]
pub struct Uniform<X: SampleUniform>(X::Sampler);
// HACK: internals are public for benches
pub struct Uniform<X: SampleUniform>(pub X::Sampler);
dhardy marked this conversation as resolved.
Show resolved Hide resolved

impl<X: SampleUniform> Uniform<X> {
/// Create a new `Uniform` instance, which samples uniformly from the half
Expand Down Expand Up @@ -995,6 +996,38 @@ macro_rules! uniform_float_impl {
}
}
}

#[inline]
fn sample_single_inclusive<R: Rng + ?Sized, B1, B2>(low_b: B1, high_b: B2, rng: &mut R) -> Result<Self::X, Error>
where
B1: SampleBorrow<Self::X> + Sized,
B2: SampleBorrow<Self::X> + Sized,
{
let low = *low_b.borrow();
let high = *high_b.borrow();
#[cfg(debug_assertions)]
if !low.all_finite() || !high.all_finite() {
return Err(Error::NonFinite);
}
if !low.all_le(high) {
return Err(Error::EmptyRange);
}
let scale = high - low;
if !scale.all_finite() {
return Err(Error::NonFinite);
}

// Generate a value in the range [1, 2)
let value1_2 =
(rng.gen::<$uty>() >> $uty::splat($bits_to_discard)).into_float_with_exponent(0);

// Get a value in the range [0, 1) to avoid overflow when multiplying by scale
let value0_1 = value1_2 - <$ty>::splat(1.0);

// Doing multiply before addition allows some architectures
// to use a single instruction.
Ok(value0_1 * scale + low)
}
}
};
}
Expand Down Expand Up @@ -1367,6 +1400,9 @@ mod tests {
let v = <$ty as SampleUniform>::Sampler
::sample_single(low, high, &mut rng).unwrap().extract(lane);
assert!(low_scalar <= v && v < high_scalar);
let v = <$ty as SampleUniform>::Sampler
::sample_single_inclusive(low, high, &mut rng).unwrap().extract(lane);
assert!(low_scalar <= v && v <= high_scalar);
}

assert_eq!(
Expand All @@ -1379,8 +1415,19 @@ mod tests {
assert_eq!(<$ty as SampleUniform>::Sampler
::sample_single(low, high, &mut zero_rng).unwrap()
.extract(lane), low_scalar);
assert_eq!(<$ty as SampleUniform>::Sampler
::sample_single_inclusive(low, high, &mut zero_rng).unwrap()
.extract(lane), low_scalar);

assert!(max_rng.sample(my_uniform).extract(lane) < high_scalar);
assert!(max_rng.sample(my_incl_uniform).extract(lane) <= high_scalar);
// sample_single cannot cope with max_rng:
// assert!(<$ty as SampleUniform>::Sampler
// ::sample_single(low, high, &mut max_rng).unwrap()
// .extract(lane) < high_scalar);
assert!(<$ty as SampleUniform>::Sampler
::sample_single_inclusive(low, high, &mut max_rng).unwrap()
.extract(lane) <= high_scalar);

// Don't run this test for really tiny differences between high and low
// since for those rounding might result in selecting high for a very
Expand Down