Skip to content

Commit 22d0756

Browse files
authored
Uniform sampling: use Canon's method, Lemire's method (#1287)
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
1 parent 0f5af66 commit 22d0756

File tree

7 files changed

+215
-120
lines changed

7 files changed

+215
-120
lines changed

.github/workflows/test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,13 @@ jobs:
7979
run: |
8080
cargo test --target ${{ matrix.target }} --features=nightly
8181
cargo test --target ${{ matrix.target }} --all-features
82-
cargo test --target ${{ matrix.target }} --benches --features=nightly
82+
cargo test --target ${{ matrix.target }} --benches --features=small_rng,nightly
8383
cargo test --target ${{ matrix.target }} --manifest-path rand_distr/Cargo.toml --benches
8484
cargo test --target ${{ matrix.target }} --lib --tests --no-default-features
8585
- name: Test rand
8686
run: |
8787
cargo test --target ${{ matrix.target }} --lib --tests --no-default-features
88-
cargo build --target ${{ matrix.target }} --no-default-features --features alloc,getrandom,small_rng
88+
cargo build --target ${{ matrix.target }} --no-default-features --features alloc,getrandom,small_rng,unbiased
8989
cargo test --target ${{ matrix.target }} --lib --tests --no-default-features --features=alloc,getrandom,small_rng
9090
cargo test --target ${{ matrix.target }} --examples
9191
- name: Test rand (all stable features)

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.
1212
### Distributions
1313
- `{Uniform, UniformSampler}::{new, new_inclusive}` return a `Result` (instead of potentially panicking) (#1229)
1414
- `Uniform` implements `TryFrom` instead of `From` for ranges (#1229)
15+
- `Uniform` now uses Canon's method (single sampling) / Lemire's method (distribution sampling) for faster sampling (breaks value stability; #1287)
1516

1617
### Other
1718
- Simpler and faster implementation of Floyd's F2 (#1277). This

Cargo.toml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ std_rng = ["rand_chacha"]
5151
# Option: enable SmallRng
5252
small_rng = []
5353

54+
# Option: use unbiased sampling for algorithms supporting this option: Uniform distribution.
55+
# By default, bias affecting no more than one in 2^48 samples is accepted.
56+
# Note: enabling this option is expected to affect reproducibility of results.
57+
unbiased = []
58+
5459
[workspace]
5560
members = [
5661
"rand_core",
@@ -76,6 +81,10 @@ bincode = "1.2.1"
7681
rayon = "1.5.3"
7782
criterion = { version = "0.4" }
7883

84+
[[bench]]
85+
name = "uniform"
86+
harness = false
87+
7988
[[bench]]
8089
name = "seq_choose"
8190
path = "benches/seq_choose.rs"
@@ -84,4 +93,4 @@ harness = false
8493
[[bench]]
8594
name = "shuffle"
8695
path = "benches/shuffle.rs"
87-
harness = false
96+
harness = false

benches/uniform.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright 2021 Developers of the Rand project.
2+
//
3+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5+
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6+
// option. This file may not be copied, modified, or distributed
7+
// except according to those terms.
8+
9+
//! Implement benchmarks for uniform distributions over integer types
10+
11+
use core::time::Duration;
12+
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
13+
use rand::distributions::uniform::{SampleRange, Uniform};
14+
use rand::prelude::*;
15+
use rand_chacha::ChaCha8Rng;
16+
use rand_pcg::{Pcg32, Pcg64};
17+
18+
const WARM_UP_TIME: Duration = Duration::from_millis(1000);
19+
const MEASUREMENT_TIME: Duration = Duration::from_secs(3);
20+
const SAMPLE_SIZE: usize = 100_000;
21+
const N_RESAMPLES: usize = 10_000;
22+
23+
macro_rules! sample {
24+
($R:ty, $T:ty, $U:ty, $g:expr) => {
25+
$g.bench_function(BenchmarkId::new(stringify!($R), "single"), |b| {
26+
let mut rng = <$R>::from_entropy();
27+
let x = rng.gen::<$U>();
28+
let bits = (<$T>::BITS / 2);
29+
let mask = (1 as $U).wrapping_neg() >> bits;
30+
let range = (x >> bits) * (x & mask);
31+
let low = <$T>::MIN;
32+
let high = low.wrapping_add(range as $T);
33+
34+
b.iter(|| (low..=high).sample_single(&mut rng));
35+
});
36+
37+
$g.bench_function(BenchmarkId::new(stringify!($R), "distr"), |b| {
38+
let mut rng = <$R>::from_entropy();
39+
let x = rng.gen::<$U>();
40+
let bits = (<$T>::BITS / 2);
41+
let mask = (1 as $U).wrapping_neg() >> bits;
42+
let range = (x >> bits) * (x & mask);
43+
let low = <$T>::MIN;
44+
let high = low.wrapping_add(range as $T);
45+
let dist = Uniform::<$T>::new_inclusive(<$T>::MIN, high).unwrap();
46+
47+
b.iter(|| dist.sample(&mut rng));
48+
});
49+
};
50+
51+
($c:expr, $T:ty, $U:ty) => {{
52+
let mut g = $c.benchmark_group(concat!("sample", stringify!($T)));
53+
g.sample_size(SAMPLE_SIZE);
54+
g.warm_up_time(WARM_UP_TIME);
55+
g.measurement_time(MEASUREMENT_TIME);
56+
g.nresamples(N_RESAMPLES);
57+
sample!(SmallRng, $T, $U, g);
58+
sample!(ChaCha8Rng, $T, $U, g);
59+
sample!(Pcg32, $T, $U, g);
60+
sample!(Pcg64, $T, $U, g);
61+
g.finish();
62+
}};
63+
}
64+
65+
fn sample(c: &mut Criterion) {
66+
sample!(c, i8, u8);
67+
sample!(c, i16, u16);
68+
sample!(c, i32, u32);
69+
sample!(c, i64, u64);
70+
sample!(c, i128, u128);
71+
}
72+
73+
criterion_group! {
74+
name = benches;
75+
config = Criterion::default();
76+
targets = sample
77+
}
78+
criterion_main!(benches);

0 commit comments

Comments
 (0)