Skip to content

Commit

Permalink
Uniform sampling: use Canon's method, Lemire's method (#1287)
Browse files Browse the repository at this point in the history
Also:

* Add uniform distribution benchmarks
* Add "unbiased" feature flag
* Fix feature simd_support
* Uniform: impl PartialEq, Eq where possible
* CI: benches now require small_rng; build-test unbiased
  • Loading branch information
dhardy committed Mar 24, 2023
1 parent 0f5af66 commit 22d0756
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 120 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,13 @@ jobs:
run: |
cargo test --target ${{ matrix.target }} --features=nightly
cargo test --target ${{ matrix.target }} --all-features
cargo test --target ${{ matrix.target }} --benches --features=nightly
cargo test --target ${{ matrix.target }} --benches --features=small_rng,nightly
cargo test --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml --benches
cargo test --target ${{ matrix.target }} --lib --tests --no-default-features
- name: Test rand
run: |
cargo test --target ${{ matrix.target }} --lib --tests --no-default-features
cargo build --target ${{ matrix.target }} --no-default-features --features alloc,getrandom,small_rng
cargo build --target ${{ matrix.target }} --no-default-features --features alloc,getrandom,small_rng,unbiased
cargo test --target ${{ matrix.target }} --lib --tests --no-default-features --features=alloc,getrandom,small_rng
cargo test --target ${{ matrix.target }} --examples
- name: Test rand (all stable features)
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.
### Distributions
- `{Uniform, UniformSampler}::{new, new_inclusive}` return a `Result` (instead of potentially panicking) (#1229)
- `Uniform` implements `TryFrom` instead of `From` for ranges (#1229)
- `Uniform` now uses Canon's method (single sampling) / Lemire's method (distribution sampling) for faster sampling (breaks value stability; #1287)

### Other
- Simpler and faster implementation of Floyd's F2 (#1277). This
Expand Down
11 changes: 10 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ std_rng = ["rand_chacha"]
# Option: enable SmallRng
small_rng = []

# Option: use unbiased sampling for algorithms supporting this option: Uniform distribution.
# By default, bias affecting no more than one in 2^48 samples is accepted.
# Note: enabling this option is expected to affect reproducibility of results.
unbiased = []

[workspace]
members = [
"rand_core",
Expand All @@ -76,6 +81,10 @@ bincode = "1.2.1"
rayon = "1.5.3"
criterion = { version = "0.4" }

[[bench]]
name = "uniform"
harness = false

[[bench]]
name = "seq_choose"
path = "benches/seq_choose.rs"
Expand All @@ -84,4 +93,4 @@ harness = false
[[bench]]
name = "shuffle"
path = "benches/shuffle.rs"
harness = false
harness = false
78 changes: 78 additions & 0 deletions benches/uniform.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright 2021 Developers of the Rand project.
//
// 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 integer types

use core::time::Duration;
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use rand::distributions::uniform::{SampleRange, Uniform};
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! sample {
($R:ty, $T:ty, $U:ty, $g:expr) => {
$g.bench_function(BenchmarkId::new(stringify!($R), "single"), |b| {
let mut rng = <$R>::from_entropy();
let x = rng.gen::<$U>();
let bits = (<$T>::BITS / 2);
let mask = (1 as $U).wrapping_neg() >> bits;
let range = (x >> bits) * (x & mask);
let low = <$T>::MIN;
let high = low.wrapping_add(range as $T);

b.iter(|| (low..=high).sample_single(&mut rng));
});

$g.bench_function(BenchmarkId::new(stringify!($R), "distr"), |b| {
let mut rng = <$R>::from_entropy();
let x = rng.gen::<$U>();
let bits = (<$T>::BITS / 2);
let mask = (1 as $U).wrapping_neg() >> bits;
let range = (x >> bits) * (x & mask);
let low = <$T>::MIN;
let high = low.wrapping_add(range as $T);
let dist = Uniform::<$T>::new_inclusive(<$T>::MIN, high).unwrap();

b.iter(|| dist.sample(&mut rng));
});
};

($c:expr, $T:ty, $U:ty) => {{
let mut g = $c.benchmark_group(concat!("sample", stringify!($T)));
g.sample_size(SAMPLE_SIZE);
g.warm_up_time(WARM_UP_TIME);
g.measurement_time(MEASUREMENT_TIME);
g.nresamples(N_RESAMPLES);
sample!(SmallRng, $T, $U, g);
sample!(ChaCha8Rng, $T, $U, g);
sample!(Pcg32, $T, $U, g);
sample!(Pcg64, $T, $U, g);
g.finish();
}};
}

fn sample(c: &mut Criterion) {
sample!(c, i8, u8);
sample!(c, i16, u16);
sample!(c, i32, u32);
sample!(c, i64, u64);
sample!(c, i128, u128);
}

criterion_group! {
name = benches;
config = Criterion::default();
targets = sample
}
criterion_main!(benches);

0 comments on commit 22d0756

Please sign in to comment.