From bd6b9c9d779629ff4f1ceb4c66927bfb97d2b29a Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Thu, 17 Nov 2022 18:33:48 +0000 Subject: [PATCH 01/26] Added new versions of choose and choose_stable --- benches/seq.rs | 579 ++++++++++++++++++++++++++++++++++++++-- src/seq/coin_flipper.rs | 285 ++++++++++++++++++++ src/seq/mod.rs | 254 +++++++++++++++++- 3 files changed, 1098 insertions(+), 20 deletions(-) create mode 100644 src/seq/coin_flipper.rs diff --git a/benches/seq.rs b/benches/seq.rs index 5b3a846f60..04009e1c60 100644 --- a/benches/seq.rs +++ b/benches/seq.rs @@ -19,6 +19,7 @@ use core::mem::size_of; // We force use of 32-bit RNG since seq code is optimised for use with 32-bit // generators on all platforms. +use rand_chacha::ChaCha20Rng as CryptoRng; use rand_pcg::Pcg32 as SmallRng; const RAND_BENCH_N: u64 = 1000; @@ -120,30 +121,570 @@ impl Iterator for WindowHintedIterator< } } -#[bench] -fn seq_iter_unhinted_choose_from_1000(b: &mut Bencher) { - let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); - let x: &[usize] = &[1; 1000]; - b.iter(|| { - UnhintedIterator { iter: x.iter() } - .choose(&mut rng) - .unwrap() - }) +macro_rules! bench_seq_iter_size_hinted { + ($name:ident,$rng:ident, $fn:ident, $length:expr) => { + #[bench] + fn $name(b: &mut Bencher) { + let mut rng = $rng::from_rng(thread_rng()).unwrap(); + let x: &mut [usize] = &mut [1; $length]; + for (i, r) in x.iter_mut().enumerate() { + *r = i; + } + b.iter(|| { + let mut s = 0; + for _ in 0..RAND_BENCH_N { + s += x.iter().$fn(&mut rng).unwrap(); + } + s + }); + b.bytes = size_of::() as u64 * crate::RAND_BENCH_N; + } + }; } -#[bench] -fn seq_iter_window_hinted_choose_from_1000(b: &mut Bencher) { - let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); - let x: &[usize] = &[1; 1000]; - b.iter(|| { - WindowHintedIterator { - iter: x.iter(), - window_size: 7, +macro_rules! bench_seq_iter_unhinted { + ($name:ident,$rng:ident, $fn:ident, $length:expr) => { + #[bench] + fn $name(b: &mut Bencher) { + let mut rng = $rng::from_rng(thread_rng()).unwrap(); + let x: &[usize] = &[1; $length]; + b.iter(|| UnhintedIterator { iter: x.iter() }.$fn(&mut rng).unwrap()) } - .choose(&mut rng) - }) + }; } +macro_rules! bench_seq_iter_window_hinted { + ($name:ident,$rng:ident, $fn:ident, $length:expr) => { + #[bench] + fn $name(b: &mut Bencher) { + let mut rng = $rng::from_rng(thread_rng()).unwrap(); + let x: &[usize] = &[1; $length]; + b.iter(|| { + WindowHintedIterator { + iter: x.iter(), + window_size: 7, + } + .$fn(&mut rng) + .unwrap() + }) + } + }; +} + +//Size Hinted + +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_10000_cryptoRng_old_version, + CryptoRng, + choose, + 10000 +); +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_10000_cryptoRng_new_version, + CryptoRng, + choose_new_version, + 10000 +); +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_10000_smallRng_old_version, + SmallRng, + choose, + 10000 +); +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_10000_smallRng_new_version, + SmallRng, + choose_new_version, + 10000 +); +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_1000_cryptoRng_old_version, + CryptoRng, + choose, + 1000 +); +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_1000_cryptoRng_new_version, + CryptoRng, + choose_new_version, + 1000 +); +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_1000_smallRng_old_version, + SmallRng, + choose, + 1000 +); +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_1000_smallRng_new_version, + SmallRng, + choose_new_version, + 1000 +); + +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_100_cryptoRng_old_version, + CryptoRng, + choose, + 100 +); +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_100_cryptoRng_new_version, + CryptoRng, + choose_new_version, + 100 +); +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_100_smallRng_old_version, + SmallRng, + choose, + 100 +); +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_100_smallRng_new_version, + SmallRng, + choose_new_version, + 100 +); +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_10_smallRng_old_version, + SmallRng, + choose, + 10 +); +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_10_smallRng_new_version, + SmallRng, + choose_new_version, + 10 +); + +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_10_cryptoRng_old_version, + CryptoRng, + choose, + 10 +); +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_10_cryptoRng_new_version, + CryptoRng, + choose_new_version, + 10 +); + +//Unhinted + +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_10000_cryptoRng_old_version, + CryptoRng, + choose, + 10000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_10000_cryptoRng_new_version, + CryptoRng, + choose_new_version, + 10000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_10000_smallRng_old_version, + SmallRng, + choose, + 10000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_10000_smallRng_new_version, + SmallRng, + choose_new_version, + 10000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_1000_cryptoRng_old_version, + CryptoRng, + choose, + 1000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_1000_cryptoRng_new_version, + CryptoRng, + choose_new_version, + 1000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_1000_smallRng_old_version, + SmallRng, + choose, + 1000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_1000_smallRng_new_version, + SmallRng, + choose_new_version, + 1000 +); + +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_100_cryptoRng_old_version, + CryptoRng, + choose, + 100 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_100_cryptoRng_new_version, + CryptoRng, + choose_new_version, + 100 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_100_smallRng_old_version, + SmallRng, + choose, + 100 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_100_smallRng_new_version, + SmallRng, + choose_new_version, + 100 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_10_smallRng_old_version, + SmallRng, + choose, + 10 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_10_smallRng_new_version, + SmallRng, + choose_new_version, + 10 +); + +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_10_cryptoRng_old_version, + CryptoRng, + choose, + 10 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_10_cryptoRng_new_version, + CryptoRng, + choose_new_version, + 10 +); + +// Window hinted + +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_10000_cryptoRng_old_version, + CryptoRng, + choose, + 10000 +); +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_10000_cryptoRng_new_version, + CryptoRng, + choose_new_version, + 10000 +); +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_10000_smallRng_old_version, + SmallRng, + choose, + 10000 +); +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_10000_smallRng_new_version, + SmallRng, + choose_new_version, + 10000 +); +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_1000_cryptoRng_old_version, + CryptoRng, + choose, + 1000 +); +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_1000_cryptoRng_new_version, + CryptoRng, + choose_new_version, + 1000 +); +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_1000_smallRng_old_version, + SmallRng, + choose, + 1000 +); +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_1000_smallRng_new_version, + SmallRng, + choose_new_version, + 1000 +); + +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_100_cryptoRng_old_version, + CryptoRng, + choose, + 100 +); +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_100_cryptoRng_new_version, + CryptoRng, + choose_new_version, + 100 +); +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_100_smallRng_old_version, + SmallRng, + choose, + 100 +); +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_100_smallRng_new_version, + SmallRng, + choose_new_version, + 100 +); +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_10_smallRng_old_version, + SmallRng, + choose, + 10 +); +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_10_smallRng_new_version, + SmallRng, + choose_new_version, + 10 +); +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_10_cryptoRng_old_version, + CryptoRng, + choose, + 10 +); +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_10_cryptoRng_new_version, + CryptoRng, + choose_new_version, + 10 +); + +//Choose Stable +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_10000_cryptoRng_old_version, + CryptoRng, + choose_stable, + 10000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_10000_cryptoRng_new_version, + CryptoRng, + choose_stable_new_version, + 10000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_10000_smallRng_old_version, + SmallRng, + choose_stable, + 10000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_10000_smallRng_new_version, + SmallRng, + choose_stable_new_version, + 10000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_1000_cryptoRng_old_version, + CryptoRng, + choose_stable, + 1000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_1000_cryptoRng_new_version, + CryptoRng, + choose_stable_new_version, + 1000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_1000_smallRng_old_version, + SmallRng, + choose_stable, + 1000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_1000_smallRng_new_version, + SmallRng, + choose_stable_new_version, + 1000 +); + +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_100_cryptoRng_old_version, + CryptoRng, + choose_stable, + 100 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_100_cryptoRng_new_version, + CryptoRng, + choose_stable_new_version, + 100 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_100_smallRng_old_version, + SmallRng, + choose_stable, + 100 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_100_smallRng_new_version, + SmallRng, + choose_stable_new_version, + 100 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_10_smallRng_old_version, + SmallRng, + choose_stable, + 10 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_10_smallRng_new_version, + SmallRng, + choose_stable_new_version, + 10 +); + +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_10_cryptoRng_old_version, + CryptoRng, + choose_stable, + 10 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_10_cryptoRng_new_version, + CryptoRng, + choose_stable_new_version, + 10 +); + +// Window hinted + +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_10000_cryptoRng_old_version, + CryptoRng, + choose_stable, + 10000 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_10000_cryptoRng_new_version, + CryptoRng, + choose_stable_new_version, + 10000 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_10000_smallRng_old_version, + SmallRng, + choose_stable, + 10000 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_10000_smallRng_new_version, + SmallRng, + choose_stable_new_version, + 10000 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_1000_cryptoRng_old_version, + CryptoRng, + choose_stable, + 1000 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_1000_cryptoRng_new_version, + CryptoRng, + choose_stable_new_version, + 1000 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_1000_smallRng_old_version, + SmallRng, + choose_stable, + 1000 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_1000_smallRng_new_version, + SmallRng, + choose_stable_new_version, + 1000 +); + +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_100_cryptoRng_old_version, + CryptoRng, + choose_stable, + 100 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_100_cryptoRng_new_version, + CryptoRng, + choose_stable_new_version, + 100 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_100_smallRng_old_version, + SmallRng, + choose_stable, + 100 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_100_smallRng_new_version, + SmallRng, + choose_stable_new_version, + 100 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_10_smallRng_old_version, + SmallRng, + choose_stable, + 10 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_10_smallRng_new_version, + SmallRng, + choose_stable_new_version, + 10 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_10_cryptoRng_old_version, + CryptoRng, + choose_stable, + 10 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_10_cryptoRng_new_version, + CryptoRng, + choose_stable_new_version, + 10 +); + +//#[bench] +// fn seq_iter_window_hinted_choose_from_1000(b: &mut Bencher) { +// let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); +// let x: &[usize] = &[1; 1000]; +// b.iter(|| { +// WindowHintedIterator { +// iter: x.iter(), +// window_size: 7, +// } +// .choose(&mut rng) +// }) +// } #[bench] fn seq_iter_choose_multiple_10_of_100(b: &mut Bencher) { let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); diff --git a/src/seq/coin_flipper.rs b/src/seq/coin_flipper.rs new file mode 100644 index 0000000000..c5477fb8e1 --- /dev/null +++ b/src/seq/coin_flipper.rs @@ -0,0 +1,285 @@ +use crate::RngCore; + +pub(crate) struct CoinFlipper { + pub rng: R, + chunk: u32, + chunk_remaining: u32, +} + +impl CoinFlipper { + pub fn new(rng: R) -> Self { + Self { + rng, + chunk: 0, + chunk_remaining: 0, + } + } + + #[inline] + /// Returns true with a probability of 1 / denominator. + /// Uses an expected two bits of randomness + pub fn gen_ratio_one_over(&mut self, denominator: usize) -> bool { + //For this case we can use an optimization, checking a large number of bits at once. If all those bits are successful, then we specialize + let n = usize::BITS - denominator.leading_zeros() - 1; + + if !self.all_next(n) { + return false; + } + + self.gen_ratio(1 << n, denominator) + } + + #[inline] + /// Returns true with a probability of numerator / denominator + /// Uses an expected two bits of randomness + fn gen_ratio(&mut self, mut numerator: usize, denominator: usize) -> bool { + // Explanation: + // We are trying to return true with a probability of n / d + // If n >= d, we can just return true + // Otherwise there are two possibilities 2n < d and 2n >= d + // In either case we flip a coin. + // If 2n < d + // If it comes up tails, return false + // If it comes up heads, double n and start again + // This is fair because (0.5 * 0) + (0.5 * 2n / d) = n / d and 2n is less than d (if 2n was greater than d we would effectively round it down to 1) + // If 2n >= d + // If it comes up tails, set n to 2n - d + // If it comes up heads, return true + // This is fair because (0.5 * 1) + (0.5 * (2n - d) / d) = n / d + + while numerator < denominator { + if let Some(next_numerator) = numerator.checked_mul(2) { + //This condition will usually be true + + if self.next() { + //Heads + numerator = next_numerator; //if 2n >= d we this will be checked at the start of the next loop + } else { + //Tails + if next_numerator < denominator { + return false; //2n < d + } + numerator = next_numerator - denominator; //2n was greater than d so set it to 2n - d + } + } else { + //Special branch just for massive numbers. + //2n > usize::max >= d so 2n >= d + if self.next() { + //heads + return true; + } + //Tails + numerator = numerator.wrapping_sub(denominator).wrapping_add(numerator); + //2n - d + } + } + true + } + + #[inline] + /// Consume one bit of randomness + /// Has a one in two chance of returning true + fn next(&mut self) -> bool { + if let Some(new_rem) = self.chunk_remaining.checked_sub(1) { + self.chunk_remaining = new_rem; + } else { + self.chunk = self.rng.next_u32(); + self.chunk_remaining = u32::BITS - 1; + }; + + let result = self.chunk.trailing_zeros() > 0; //TODO check if there is a faster test the last bit + self.chunk = self.chunk.wrapping_shr(1); + result + } + + #[inline] + /// If the next n bits of randomness are all zeroes, consume them and return true. + /// Otherwise return false and consume the number of zeroes plus one + /// Has a one in 2 to the n chance of returning true + fn all_next(&mut self, mut n: u32) -> bool { + let mut zeros = self.chunk.trailing_zeros(); + while self.chunk_remaining < n { + //Check we have enough randomness left + if zeros >= self.chunk_remaining { + n -= self.chunk_remaining; // Remaining bits are zeroes, we will need to generate more bits and continue + } else { + self.chunk_remaining -= zeros + 1; //There was a one in the remaining bits so we can consume it and continue + self.chunk >>= zeros + 1; + return false; + } + self.chunk = self.rng.next_u32(); + self.chunk_remaining = u32::BITS; + zeros = self.chunk.trailing_zeros(); + } + + let result = zeros >= n; + let bits_to_consume = if result { n } else { zeros + 1 }; + self.chunk = self.chunk.wrapping_shr(bits_to_consume); + self.chunk_remaining = self.chunk_remaining.saturating_sub(bits_to_consume); + + result + } +} + +#[cfg(test)] +mod tests { + use core::ops::Range; + + use alloc::vec::Vec; + use rand_core::Error; + + use crate::prelude::StdRng; + use crate::seq::coin_flipper::CoinFlipper; + use crate::{Rng, RngCore, SeedableRng}; + + /// How many runs to do + const RUNS: usize = 10000; + /// Different length arrays to use + const LENGTH: usize = 10000; + const START: usize = 1; + const SEED: u64 = 123; + + #[test] + pub fn test_one_over_for_big_numbers() { + let rng = get_rng(); + + let mut coin_flipper = CoinFlipper::new(rng); + + let mut count = 0; + for _ in 0..LENGTH { + if coin_flipper.gen_ratio_one_over((2_i64.pow(33) + 1) as usize) { + count += 1; + } + } + + let average_gens = ((LENGTH) as f64) / (coin_flipper.rng.count as f64); + + // println!( + // "Gens: {} (1 per {} gens)", + // coin_flipper.rng.count, average_gens + // ); + // println!("Count: {count}"); + assert_contains(15.5..16.5, &average_gens); //Should be about 16 + + assert!(count < 2); //Should not get it twice + } + + #[test] + pub fn test_gen_ratio_for_big_numbers() { + let rng = get_rng(); + let mut coin_flipper = CoinFlipper::new(rng); + + let mut count = 0; + for _ in 0..RUNS { + if coin_flipper.gen_ratio((usize::MAX / 2) + 1, usize::MAX) { + count += 1; + } + } + + let average_gens = (RUNS as f64) / (coin_flipper.rng.count as f64); + + // println!( + // "Gens: {} (1 per {} gens)", + // coin_flipper.rng.count, average_gens + // ); + + // println!("Count: {count}"); + + let mean = (count as f64) / RUNS as f64; + + //println!("Mean: {mean}"); + assert_contains(15.5..16.5, &average_gens); //Should be about 16 (32 bit / 2 bits per gen) + assert_contains(0.45..0.55, &mean); //Should be about 0.5 + } + + #[test] + pub fn test_coin_flipper_gen_ratio() { + let rng = get_rng(); + let mut coin_flipper = CoinFlipper::new(rng); + + let mut counts: alloc::vec::Vec<_> = Default::default(); + for d in START..=LENGTH { + let mut count = 0; + for _ in 0..RUNS { + if coin_flipper.gen_ratio_one_over(d) { + count += 1; + } + } + counts.push(count); + } + + let adjusted_counts: alloc::vec::Vec<_> = counts + .iter() + .enumerate() + .map(|(i, &x)| (i + START) * x) + .map(|z| (z as f64) / (RUNS as f64)) + .collect(); + + let average_gens = ((RUNS * LENGTH) as f64) / (coin_flipper.rng.count as f64); + + // println!( + // "Gens: {} (1 per {} gens)", + // coin_flipper.rng.count, average_gens + // ); + + let (mean, _variance, standard_deviation) = get_stats(adjusted_counts); + + //println!("mean: {mean}, variance: {variance}, standard deviation: {standard_deviation}"); + + assert_contains(15.5..16.5, &average_gens); //Should be just over 16 gens per gen_ratio + assert_contains(0.95..1.05, &mean); //Should be about 1 because we are adjusting + assert_contains(0.0..10.0, &standard_deviation); + } + + fn get_rng() -> CountingRng { + let inner = StdRng::seed_from_u64(SEED); + CountingRng { + rng: inner, + count: 0, + } + } + + pub fn get_stats(vec: Vec) -> (f64, f64, f64) { + let mean: f64 = vec.iter().map(|&x| x as f64 / (vec.len() as f64)).sum(); + let variance: f64 = vec + .iter() + .map(|&x| f64::powi((x as f64) - mean, 2) / (vec.len() as f64)) + .sum(); + let standard_deviation = f64::sqrt(variance); + + (mean, variance, standard_deviation) + } + + fn assert_contains(range: Range, n: &f64) { + if !range.contains(n) { + panic!("The range {:?} does not contain {n}", range) + } + } + + struct CountingRng { + pub rng: Inner, + pub count: usize, + } + + impl RngCore for CountingRng { + fn next_u32(&mut self) -> u32 { + self.count += 1; + self.rng.next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.count += 1; + self.rng.next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.count += 1; + self.rng.fill_bytes(dest) + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { + self.count += 1; + self.rng.try_fill_bytes(dest) + } + } +} diff --git a/src/seq/mod.rs b/src/seq/mod.rs index 420ef253d5..cc544759c0 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -24,7 +24,7 @@ //! `usize` indices are sampled as a `u32` where possible (also providing a //! small performance boost in some cases). - +mod coin_flipper; #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub mod index; @@ -38,6 +38,8 @@ use crate::distributions::uniform::{SampleBorrow, SampleUniform}; #[cfg(feature = "alloc")] use crate::distributions::WeightedError; use crate::Rng; +use self::coin_flipper::CoinFlipper; + /// Extension trait on slices, providing random mutation and sampling methods. /// /// This trait is implemented on all `[T]` slice types, providing several @@ -359,6 +361,62 @@ pub trait IteratorRandom: Iterator + Sized { } } + /// New version of choose + fn choose_new_version(mut self, rng: &mut R) -> Option + where + R: Rng + ?Sized, + { + let (mut lower, mut upper) = self.size_hint(); + let mut result = None; + + // Handling for this condition outside the loop allows the optimizer to eliminate the loop + // when the Iterator is an ExactSizeIterator. This has a large performance impact on e.g. + // seq_iter_choose_from_1000. + if upper == Some(lower) { + return if lower == 0 { + None + } else { + self.nth(gen_index(rng, lower)) + }; + } + + let mut coin_flipper = coin_flipper::CoinFlipper::new(rng); + let mut consumed = 0; + + // Continue until the iterator is exhausted + loop { + if lower > 1 { + let ix = gen_index(coin_flipper.rng, lower + consumed); + let skip = if ix < lower { + result = self.nth(ix); + lower - (ix + 1) + } else { + lower + }; + if upper == Some(lower) { + return result; + } + consumed += lower; + if skip > 0 { + self.nth(skip - 1); + } + } else { + let elem = self.next(); + if elem.is_none() { + return result; + } + consumed += 1; + if coin_flipper.gen_ratio_one_over(consumed) { + result = elem; + } + } + + let hint = self.size_hint(); + lower = hint.0; + upper = hint.1; + } + } + /// Choose one element at random from the iterator. /// /// Returns `None` if and only if the iterator is empty. @@ -417,6 +475,49 @@ pub trait IteratorRandom: Iterator + Sized { } } + /// New version of choose stable + fn choose_stable_new_version(mut self, rng: &mut R) -> Option + where R: Rng + ?Sized { + let mut consumed = 0; + let mut result = None; + let mut coin_flipper = CoinFlipper::new(rng); + + loop { + // Currently the only way to skip elements is `nth()`. So we need to + // store what index to access next here. + // This should be replaced by `advance_by()` once it is stable: + // https://github.com/rust-lang/rust/issues/77404 + let mut next = 0; + + + let (lower, _) = self.size_hint(); + if lower >= 2 { + let highest_selected = (0..lower) + .filter(|ix| coin_flipper.gen_ratio_one_over(consumed+ix+1)) + .last(); + + consumed += lower; + next = lower; + + if let Some(ix) = highest_selected { + result = self.nth(ix); + next -= ix + 1; + debug_assert!(result.is_some(), "iterator shorter than size_hint().0"); + } + } + + let elem = self.nth(next); + if elem.is_none() { + return result + } + + if coin_flipper.gen_ratio_one_over(consumed+1) { + result = elem; + } + consumed += 1; + } + } + /// Collects values at random from the iterator into a supplied buffer /// until that buffer is filled. /// @@ -863,6 +964,60 @@ mod test { assert_eq!((0..0).choose(r), None); assert_eq!(UnhintedIterator { iter: 0..0 }.choose(r), None); } + + #[test] + #[cfg_attr(miri, ignore)] // Miri is too slow + fn test_iterator_choose_new_version() { + let r = &mut crate::test::rng(109); + fn test_iter + Clone>(r: &mut R, iter: Iter) { + let mut chosen = [0i32; 9]; + for _ in 0..1000 { + let picked = iter.clone().choose_new_version(r).unwrap(); + chosen[picked] += 1; + } + for count in chosen.iter() { + // Samples should follow Binomial(1000, 1/9) + // Octave: binopdf(x, 1000, 1/9) gives the prob of *count == x + // Note: have seen 153, which is unlikely but not impossible. + assert!( + 72 < *count && *count < 154, + "count not close to 1000/9: {}", + count + ); + } + } + + test_iter(r, 0..9); + test_iter(r, [0, 1, 2, 3, 4, 5, 6, 7, 8].iter().cloned()); + #[cfg(feature = "alloc")] + test_iter(r, (0..9).collect::>().into_iter()); + test_iter(r, UnhintedIterator { iter: 0..9 }); + test_iter(r, ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: false, + }); + test_iter(r, ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: true, + }); + test_iter(r, WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: false, + }); + test_iter(r, WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: true, + }); + + assert_eq!((0..0).choose(r), None); + assert_eq!(UnhintedIterator { iter: 0..0 }.choose(r), None); + } #[test] #[cfg_attr(miri, ignore)] // Miri is too slow @@ -917,6 +1072,60 @@ mod test { assert_eq!((0..0).choose(r), None); assert_eq!(UnhintedIterator { iter: 0..0 }.choose(r), None); } + + #[test] + #[cfg_attr(miri, ignore)] // Miri is too slow + fn test_iterator_choose_stable_new_version() { + let r = &mut crate::test::rng(109); + fn test_iter + Clone>(r: &mut R, iter: Iter) { + let mut chosen = [0i32; 9]; + for _ in 0..1000 { + let picked = iter.clone().choose_stable_new_version(r).unwrap(); + chosen[picked] += 1; + } + for count in chosen.iter() { + // Samples should follow Binomial(1000, 1/9) + // Octave: binopdf(x, 1000, 1/9) gives the prob of *count == x + // Note: have seen 153, which is unlikely but not impossible. + assert!( + 72 < *count && *count < 154, + "count not close to 1000/9: {}", + count + ); + } + } + + test_iter(r, 0..9); + test_iter(r, [0, 1, 2, 3, 4, 5, 6, 7, 8].iter().cloned()); + #[cfg(feature = "alloc")] + test_iter(r, (0..9).collect::>().into_iter()); + test_iter(r, UnhintedIterator { iter: 0..9 }); + test_iter(r, ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: false, + }); + test_iter(r, ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: true, + }); + test_iter(r, WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: false, + }); + test_iter(r, WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: true, + }); + + assert_eq!((0..0).choose(r), None); + assert_eq!(UnhintedIterator { iter: 0..0 }.choose(r), None); + } #[test] #[cfg_attr(miri, ignore)] // Miri is too slow @@ -960,6 +1169,49 @@ mod test { hint_total_size: true, }), reference); } + + #[test] + #[cfg_attr(miri, ignore)] // Miri is too slow + fn test_iterator_choose_stable_stability_new_version() { + fn test_iter(iter: impl Iterator + Clone) -> [i32; 9] { + let r = &mut crate::test::rng(109); + let mut chosen = [0i32; 9]; + for _ in 0..1000 { + let picked = iter.clone().choose_stable(r).unwrap(); + chosen[picked] += 1; + } + chosen + } + + let reference = test_iter(0..9); + assert_eq!(test_iter([0, 1, 2, 3, 4, 5, 6, 7, 8].iter().cloned()), reference); + + #[cfg(feature = "alloc")] + assert_eq!(test_iter((0..9).collect::>().into_iter()), reference); + assert_eq!(test_iter(UnhintedIterator { iter: 0..9 }), reference); + assert_eq!(test_iter(ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: false, + }), reference); + assert_eq!(test_iter(ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: true, + }), reference); + assert_eq!(test_iter(WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: false, + }), reference); + assert_eq!(test_iter(WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: true, + }), reference); + } #[test] #[cfg_attr(miri, ignore)] // Miri is too slow From eb5672b43baa80c5c57fe91bd3eaa9348b02c7a7 Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Thu, 17 Nov 2022 19:09:13 +0000 Subject: [PATCH 02/26] Removed coin_flipper tests which were unnecessary and not building on ci --- src/seq/coin_flipper.rs | 163 ---------------------------------------- 1 file changed, 163 deletions(-) diff --git a/src/seq/coin_flipper.rs b/src/seq/coin_flipper.rs index c5477fb8e1..527619ab79 100644 --- a/src/seq/coin_flipper.rs +++ b/src/seq/coin_flipper.rs @@ -120,166 +120,3 @@ impl CoinFlipper { result } } - -#[cfg(test)] -mod tests { - use core::ops::Range; - - use alloc::vec::Vec; - use rand_core::Error; - - use crate::prelude::StdRng; - use crate::seq::coin_flipper::CoinFlipper; - use crate::{Rng, RngCore, SeedableRng}; - - /// How many runs to do - const RUNS: usize = 10000; - /// Different length arrays to use - const LENGTH: usize = 10000; - const START: usize = 1; - const SEED: u64 = 123; - - #[test] - pub fn test_one_over_for_big_numbers() { - let rng = get_rng(); - - let mut coin_flipper = CoinFlipper::new(rng); - - let mut count = 0; - for _ in 0..LENGTH { - if coin_flipper.gen_ratio_one_over((2_i64.pow(33) + 1) as usize) { - count += 1; - } - } - - let average_gens = ((LENGTH) as f64) / (coin_flipper.rng.count as f64); - - // println!( - // "Gens: {} (1 per {} gens)", - // coin_flipper.rng.count, average_gens - // ); - // println!("Count: {count}"); - assert_contains(15.5..16.5, &average_gens); //Should be about 16 - - assert!(count < 2); //Should not get it twice - } - - #[test] - pub fn test_gen_ratio_for_big_numbers() { - let rng = get_rng(); - let mut coin_flipper = CoinFlipper::new(rng); - - let mut count = 0; - for _ in 0..RUNS { - if coin_flipper.gen_ratio((usize::MAX / 2) + 1, usize::MAX) { - count += 1; - } - } - - let average_gens = (RUNS as f64) / (coin_flipper.rng.count as f64); - - // println!( - // "Gens: {} (1 per {} gens)", - // coin_flipper.rng.count, average_gens - // ); - - // println!("Count: {count}"); - - let mean = (count as f64) / RUNS as f64; - - //println!("Mean: {mean}"); - assert_contains(15.5..16.5, &average_gens); //Should be about 16 (32 bit / 2 bits per gen) - assert_contains(0.45..0.55, &mean); //Should be about 0.5 - } - - #[test] - pub fn test_coin_flipper_gen_ratio() { - let rng = get_rng(); - let mut coin_flipper = CoinFlipper::new(rng); - - let mut counts: alloc::vec::Vec<_> = Default::default(); - for d in START..=LENGTH { - let mut count = 0; - for _ in 0..RUNS { - if coin_flipper.gen_ratio_one_over(d) { - count += 1; - } - } - counts.push(count); - } - - let adjusted_counts: alloc::vec::Vec<_> = counts - .iter() - .enumerate() - .map(|(i, &x)| (i + START) * x) - .map(|z| (z as f64) / (RUNS as f64)) - .collect(); - - let average_gens = ((RUNS * LENGTH) as f64) / (coin_flipper.rng.count as f64); - - // println!( - // "Gens: {} (1 per {} gens)", - // coin_flipper.rng.count, average_gens - // ); - - let (mean, _variance, standard_deviation) = get_stats(adjusted_counts); - - //println!("mean: {mean}, variance: {variance}, standard deviation: {standard_deviation}"); - - assert_contains(15.5..16.5, &average_gens); //Should be just over 16 gens per gen_ratio - assert_contains(0.95..1.05, &mean); //Should be about 1 because we are adjusting - assert_contains(0.0..10.0, &standard_deviation); - } - - fn get_rng() -> CountingRng { - let inner = StdRng::seed_from_u64(SEED); - CountingRng { - rng: inner, - count: 0, - } - } - - pub fn get_stats(vec: Vec) -> (f64, f64, f64) { - let mean: f64 = vec.iter().map(|&x| x as f64 / (vec.len() as f64)).sum(); - let variance: f64 = vec - .iter() - .map(|&x| f64::powi((x as f64) - mean, 2) / (vec.len() as f64)) - .sum(); - let standard_deviation = f64::sqrt(variance); - - (mean, variance, standard_deviation) - } - - fn assert_contains(range: Range, n: &f64) { - if !range.contains(n) { - panic!("The range {:?} does not contain {n}", range) - } - } - - struct CountingRng { - pub rng: Inner, - pub count: usize, - } - - impl RngCore for CountingRng { - fn next_u32(&mut self) -> u32 { - self.count += 1; - self.rng.next_u32() - } - - fn next_u64(&mut self) -> u64 { - self.count += 1; - self.rng.next_u64() - } - - fn fill_bytes(&mut self, dest: &mut [u8]) { - self.count += 1; - self.rng.fill_bytes(dest) - } - - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - self.count += 1; - self.rng.try_fill_bytes(dest) - } - } -} From ecb11581a266f43055404a1fc50d4e0865c20ff8 Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Fri, 18 Nov 2022 18:10:14 +0000 Subject: [PATCH 03/26] Performance optimizations in coin_flipper --- src/seq/coin_flipper.rs | 112 +++++++++++++++++----------------------- src/seq/mod.rs | 6 +-- 2 files changed, 51 insertions(+), 67 deletions(-) diff --git a/src/seq/coin_flipper.rs b/src/seq/coin_flipper.rs index 527619ab79..47d82ee41b 100644 --- a/src/seq/coin_flipper.rs +++ b/src/seq/coin_flipper.rs @@ -16,23 +16,14 @@ impl CoinFlipper { } #[inline] - /// Returns true with a probability of 1 / denominator. - /// Uses an expected two bits of randomness pub fn gen_ratio_one_over(&mut self, denominator: usize) -> bool { - //For this case we can use an optimization, checking a large number of bits at once. If all those bits are successful, then we specialize - let n = usize::BITS - denominator.leading_zeros() - 1; - - if !self.all_next(n) { - return false; - } - - self.gen_ratio(1 << n, denominator) + return self.gen_ratio(1, denominator); } #[inline] /// Returns true with a probability of numerator / denominator /// Uses an expected two bits of randomness - fn gen_ratio(&mut self, mut numerator: usize, denominator: usize) -> bool { + pub fn gen_ratio(&mut self, mut numerator: usize, denominator: usize) -> bool { // Explanation: // We are trying to return true with a probability of n / d // If n >= d, we can just return true @@ -48,75 +39,68 @@ impl CoinFlipper { // This is fair because (0.5 * 1) + (0.5 * (2n - d) / d) = n / d while numerator < denominator { - if let Some(next_numerator) = numerator.checked_mul(2) { - //This condition will usually be true + //The exponent is the number of heads that need to be won in a row to not return false + let exponent = numerator.leading_zeros() - denominator.leading_zeros(); - if self.next() { - //Heads - numerator = next_numerator; //if 2n >= d we this will be checked at the start of the next loop + if exponent > 1 { + //n * 2^exp < d + if self.flip_until_tails(exponent) { + // all heads + numerator <<= exponent; } else { - //Tails - if next_numerator < denominator { - return false; //2n < d - } - numerator = next_numerator - denominator; //2n was greater than d so set it to 2n - d + // a tails somewhere + return false; } } else { - //Special branch just for massive numbers. - //2n > usize::max >= d so 2n >= d - if self.next() { - //heads - return true; + if self.flip_until_tails(1) { + numerator = numerator.saturating_mul(2); + } else { + numerator = numerator.wrapping_sub(denominator).wrapping_add(numerator); + if numerator > denominator { + return false; + } } - //Tails - numerator = numerator.wrapping_sub(denominator).wrapping_add(numerator); - //2n - d } } true } - #[inline] - /// Consume one bit of randomness - /// Has a one in two chance of returning true - fn next(&mut self) -> bool { - if let Some(new_rem) = self.chunk_remaining.checked_sub(1) { - self.chunk_remaining = new_rem; - } else { - self.chunk = self.rng.next_u32(); - self.chunk_remaining = u32::BITS - 1; - }; - - let result = self.chunk.trailing_zeros() > 0; //TODO check if there is a faster test the last bit - self.chunk = self.chunk.wrapping_shr(1); - result - } - #[inline] /// If the next n bits of randomness are all zeroes, consume them and return true. /// Otherwise return false and consume the number of zeroes plus one + /// Generates new bits of randomness when necessary (int 32 bit chunks) /// Has a one in 2 to the n chance of returning true - fn all_next(&mut self, mut n: u32) -> bool { - let mut zeros = self.chunk.trailing_zeros(); - while self.chunk_remaining < n { - //Check we have enough randomness left - if zeros >= self.chunk_remaining { - n -= self.chunk_remaining; // Remaining bits are zeroes, we will need to generate more bits and continue - } else { - self.chunk_remaining -= zeros + 1; //There was a one in the remaining bits so we can consume it and continue - self.chunk >>= zeros + 1; + fn flip_until_tails(&mut self, mut n: u32) -> bool { + //Note that zeros on the left of the chunk represent heads. It needs to be this way round because zeros are filled in when left shifting + loop { + let zeros = self.chunk.leading_zeros(); + + if zeros < n { + // The happy path - we found a 1 and can return false + // Note that because a 1 bit was detected, we cannot have run out of random bits so we don't need to check + + // First consume all of the bits read + self.chunk = self.chunk.wrapping_shl(zeros + 1); + self.chunk_remaining = self.chunk_remaining.saturating_sub(zeros + 1); return false; + } else { + // The number of zeros is larger than n + //There are two possibilities + if let Some(new_remaining) = self.chunk_remaining.checked_sub(n) { + //Those zeroes were all part of our random chunk, so throw away n bits of randomness and return true + self.chunk_remaining = new_remaining; + self.chunk <<= n; + return true; + } else { + // Some of those zeroes were part of the random chunk and some were part of the space behind it + n -= self.chunk_remaining; //Take into account the zeroes that were random + + // Generate a new chunk + self.chunk = self.rng.next_u32(); + self.chunk_remaining = 32; //TODO change back to U32::BITS + //Go back to start of loop + } } - self.chunk = self.rng.next_u32(); - self.chunk_remaining = u32::BITS; - zeros = self.chunk.trailing_zeros(); } - - let result = zeros >= n; - let bits_to_consume = if result { n } else { zeros + 1 }; - self.chunk = self.chunk.wrapping_shr(bits_to_consume); - self.chunk_remaining = self.chunk_remaining.saturating_sub(bits_to_consume); - - result } } diff --git a/src/seq/mod.rs b/src/seq/mod.rs index cc544759c0..ead88f9732 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -406,7 +406,7 @@ pub trait IteratorRandom: Iterator + Sized { return result; } consumed += 1; - if coin_flipper.gen_ratio_one_over(consumed) { + if coin_flipper.gen_ratio(1, consumed) { result = elem; } } @@ -493,7 +493,7 @@ pub trait IteratorRandom: Iterator + Sized { let (lower, _) = self.size_hint(); if lower >= 2 { let highest_selected = (0..lower) - .filter(|ix| coin_flipper.gen_ratio_one_over(consumed+ix+1)) + .filter(|ix| coin_flipper.gen_ratio(1, consumed+ix+1)) .last(); consumed += lower; @@ -511,7 +511,7 @@ pub trait IteratorRandom: Iterator + Sized { return result } - if coin_flipper.gen_ratio_one_over(consumed+1) { + if coin_flipper.gen_ratio(1, consumed+1) { result = elem; } consumed += 1; From 6a0d278b6c28735eb7b94e8e2e93bd55c493828f Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Fri, 18 Nov 2022 18:22:46 +0000 Subject: [PATCH 04/26] Clippy fixes and more documentation --- src/seq/coin_flipper.rs | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/seq/coin_flipper.rs b/src/seq/coin_flipper.rs index 47d82ee41b..b0962cf8ac 100644 --- a/src/seq/coin_flipper.rs +++ b/src/seq/coin_flipper.rs @@ -15,11 +15,6 @@ impl CoinFlipper { } } - #[inline] - pub fn gen_ratio_one_over(&mut self, denominator: usize) -> bool { - return self.gen_ratio(1, denominator); - } - #[inline] /// Returns true with a probability of numerator / denominator /// Uses an expected two bits of randomness @@ -32,7 +27,7 @@ impl CoinFlipper { // If 2n < d // If it comes up tails, return false // If it comes up heads, double n and start again - // This is fair because (0.5 * 0) + (0.5 * 2n / d) = n / d and 2n is less than d (if 2n was greater than d we would effectively round it down to 1) + // This is fair because (0.5 * 0) + (0.5 * 2n / d) = n / d and 2n is less than d (if 2n was greater than d we would effectively round it down to 1 by returning true) // If 2n >= d // If it comes up tails, set n to 2n - d // If it comes up heads, return true @@ -43,7 +38,7 @@ impl CoinFlipper { let exponent = numerator.leading_zeros() - denominator.leading_zeros(); if exponent > 1 { - //n * 2^exp < d + //n * 2^exponent < d if self.flip_until_tails(exponent) { // all heads numerator <<= exponent; @@ -51,14 +46,21 @@ impl CoinFlipper { // a tails somewhere return false; } + } + //Otherwise n * 2 is either greater than or very close to d + else if self.flip_until_tails(1) { + // Heads - double n. + // If it is now larger than d the loop will exit a + numerator = numerator.saturating_mul(2); } else { - if self.flip_until_tails(1) { - numerator = numerator.saturating_mul(2); - } else { - numerator = numerator.wrapping_sub(denominator).wrapping_add(numerator); - if numerator > denominator { - return false; - } + // Tails + // If 2n < d return false + // Else set n to 2n -d + + numerator = numerator.wrapping_sub(denominator).wrapping_add(numerator); + if numerator > denominator { + //This is equivalent to checking if 2n < d (hence the wrapping) + return false; } } } @@ -76,16 +78,16 @@ impl CoinFlipper { let zeros = self.chunk.leading_zeros(); if zeros < n { - // The happy path - we found a 1 and can return false + // The happy path - we found a 1 and can return false // Note that because a 1 bit was detected, we cannot have run out of random bits so we don't need to check // First consume all of the bits read self.chunk = self.chunk.wrapping_shl(zeros + 1); self.chunk_remaining = self.chunk_remaining.saturating_sub(zeros + 1); return false; - } else { + } else { // The number of zeros is larger than n - //There are two possibilities + //There are two possibilities if let Some(new_remaining) = self.chunk_remaining.checked_sub(n) { //Those zeroes were all part of our random chunk, so throw away n bits of randomness and return true self.chunk_remaining = new_remaining; @@ -98,7 +100,7 @@ impl CoinFlipper { // Generate a new chunk self.chunk = self.rng.next_u32(); self.chunk_remaining = 32; //TODO change back to U32::BITS - //Go back to start of loop + //Go back to start of loop } } } From b9c0b2019c863c11d61d92ff61c8ee19e7ded183 Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Fri, 18 Nov 2022 19:21:45 +0000 Subject: [PATCH 05/26] Added a correctness fix for coin_flipper --- src/seq/coin_flipper.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/seq/coin_flipper.rs b/src/seq/coin_flipper.rs index b0962cf8ac..5465f5ead2 100644 --- a/src/seq/coin_flipper.rs +++ b/src/seq/coin_flipper.rs @@ -35,7 +35,7 @@ impl CoinFlipper { while numerator < denominator { //The exponent is the number of heads that need to be won in a row to not return false - let exponent = numerator.leading_zeros() - denominator.leading_zeros(); + let exponent = (numerator.leading_zeros() - denominator.leading_zeros()).min(32); if exponent > 1 { //n * 2^exponent < d @@ -72,7 +72,9 @@ impl CoinFlipper { /// Otherwise return false and consume the number of zeroes plus one /// Generates new bits of randomness when necessary (int 32 bit chunks) /// Has a one in 2 to the n chance of returning true + /// n must be less than or equal to 32 fn flip_until_tails(&mut self, mut n: u32) -> bool { + debug_assert!(n <= 32); //If n > 32 this wil always return false //Note that zeros on the left of the chunk represent heads. It needs to be this way round because zeros are filled in when left shifting loop { let zeros = self.chunk.leading_zeros(); @@ -105,4 +107,4 @@ impl CoinFlipper { } } } -} +} \ No newline at end of file From 0ce6bfaf24d770429f4c8333285c0a0479bfce9f Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Mon, 21 Nov 2022 12:28:35 +0000 Subject: [PATCH 06/26] Update benches/seq.rs Co-authored-by: Vinzent Steinberg --- benches/seq.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/benches/seq.rs b/benches/seq.rs index 04009e1c60..f37e10caa6 100644 --- a/benches/seq.rs +++ b/benches/seq.rs @@ -673,18 +673,6 @@ bench_seq_iter_unhinted!( 10 ); -//#[bench] -// fn seq_iter_window_hinted_choose_from_1000(b: &mut Bencher) { -// let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); -// let x: &[usize] = &[1; 1000]; -// b.iter(|| { -// WindowHintedIterator { -// iter: x.iter(), -// window_size: 7, -// } -// .choose(&mut rng) -// }) -// } #[bench] fn seq_iter_choose_multiple_10_of_100(b: &mut Bencher) { let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); From 7f34c555f028a3d196790a442e88675e624b40be Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Mon, 21 Nov 2022 12:28:45 +0000 Subject: [PATCH 07/26] Update benches/seq.rs Co-authored-by: Vinzent Steinberg --- benches/seq.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benches/seq.rs b/benches/seq.rs index f37e10caa6..1d30d70d1c 100644 --- a/benches/seq.rs +++ b/benches/seq.rs @@ -122,7 +122,7 @@ impl Iterator for WindowHintedIterator< } macro_rules! bench_seq_iter_size_hinted { - ($name:ident,$rng:ident, $fn:ident, $length:expr) => { + ($name:ident, $rng:ident, $fn:ident, $length:expr) => { #[bench] fn $name(b: &mut Bencher) { let mut rng = $rng::from_rng(thread_rng()).unwrap(); From 1fe6c9f18079c359b8f65fc5567d0ff496a30ff5 Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Mon, 21 Nov 2022 12:29:28 +0000 Subject: [PATCH 08/26] Removed old version of choose and choose stable and updated value stability tests --- src/seq/mod.rs | 524 ++++++++++++++++--------------------------------- 1 file changed, 174 insertions(+), 350 deletions(-) diff --git a/src/seq/mod.rs b/src/seq/mod.rs index ead88f9732..65d6f1b9d0 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -29,13 +29,16 @@ mod coin_flipper; #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] pub mod index; -#[cfg(feature = "alloc")] use core::ops::Index; +#[cfg(feature = "alloc")] +use core::ops::Index; -#[cfg(feature = "alloc")] use alloc::vec::Vec; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; #[cfg(feature = "alloc")] use crate::distributions::uniform::{SampleBorrow, SampleUniform}; -#[cfg(feature = "alloc")] use crate::distributions::WeightedError; +#[cfg(feature = "alloc")] +use crate::distributions::WeightedError; use crate::Rng; use self::coin_flipper::CoinFlipper; @@ -79,14 +82,16 @@ pub trait SliceRandom { /// assert_eq!(choices[..0].choose(&mut rng), None); /// ``` fn choose(&self, rng: &mut R) -> Option<&Self::Item> - where R: Rng + ?Sized; + where + R: Rng + ?Sized; /// Returns a mutable reference to one random element of the slice, or /// `None` if the slice is empty. /// /// For slices, complexity is `O(1)`. fn choose_mut(&mut self, rng: &mut R) -> Option<&mut Self::Item> - where R: Rng + ?Sized; + where + R: Rng + ?Sized; /// Chooses `amount` elements from the slice at random, without repetition, /// and in random order. The returned iterator is appropriate both for @@ -115,7 +120,8 @@ pub trait SliceRandom { #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] fn choose_multiple(&self, rng: &mut R, amount: usize) -> SliceChooseIter - where R: Rng + ?Sized; + where + R: Rng + ?Sized; /// Similar to [`choose`], but where the likelihood of each outcome may be /// specified. @@ -251,7 +257,8 @@ pub trait SliceRandom { /// println!("Shuffled: {:?}", y); /// ``` fn shuffle(&mut self, rng: &mut R) - where R: Rng + ?Sized; + where + R: Rng + ?Sized; /// Shuffle a slice in place, but exit early. /// @@ -273,7 +280,8 @@ pub trait SliceRandom { fn partial_shuffle( &mut self, rng: &mut R, amount: usize, ) -> (&mut [Self::Item], &mut [Self::Item]) - where R: Rng + ?Sized; + where + R: Rng + ?Sized; } /// Extension trait on iterators, providing random sampling methods. @@ -311,58 +319,6 @@ pub trait IteratorRandom: Iterator + Sized { /// `choose` returning different elements. If you want consistent results /// and RNG usage consider using [`IteratorRandom::choose_stable`]. fn choose(mut self, rng: &mut R) -> Option - where R: Rng + ?Sized { - let (mut lower, mut upper) = self.size_hint(); - let mut consumed = 0; - let mut result = None; - - // Handling for this condition outside the loop allows the optimizer to eliminate the loop - // when the Iterator is an ExactSizeIterator. This has a large performance impact on e.g. - // seq_iter_choose_from_1000. - if upper == Some(lower) { - return if lower == 0 { - None - } else { - self.nth(gen_index(rng, lower)) - }; - } - - // Continue until the iterator is exhausted - loop { - if lower > 1 { - let ix = gen_index(rng, lower + consumed); - let skip = if ix < lower { - result = self.nth(ix); - lower - (ix + 1) - } else { - lower - }; - if upper == Some(lower) { - return result; - } - consumed += lower; - if skip > 0 { - self.nth(skip - 1); - } - } else { - let elem = self.next(); - if elem.is_none() { - return result; - } - consumed += 1; - if gen_index(rng, consumed) == 0 { - result = elem; - } - } - - let hint = self.size_hint(); - lower = hint.0; - upper = hint.1; - } - } - - /// New version of choose - fn choose_new_version(mut self, rng: &mut R) -> Option where R: Rng + ?Sized, { @@ -436,48 +392,9 @@ pub trait IteratorRandom: Iterator + Sized { /// /// [`choose`]: IteratorRandom::choose fn choose_stable(mut self, rng: &mut R) -> Option - where R: Rng + ?Sized { - let mut consumed = 0; - let mut result = None; - - loop { - // Currently the only way to skip elements is `nth()`. So we need to - // store what index to access next here. - // This should be replaced by `advance_by()` once it is stable: - // https://github.com/rust-lang/rust/issues/77404 - let mut next = 0; - - let (lower, _) = self.size_hint(); - if lower >= 2 { - let highest_selected = (0..lower) - .filter(|ix| gen_index(rng, consumed+ix+1) == 0) - .last(); - - consumed += lower; - next = lower; - - if let Some(ix) = highest_selected { - result = self.nth(ix); - next -= ix + 1; - debug_assert!(result.is_some(), "iterator shorter than size_hint().0"); - } - } - - let elem = self.nth(next); - if elem.is_none() { - return result - } - - if gen_index(rng, consumed+1) == 0 { - result = elem; - } - consumed += 1; - } - } - - /// New version of choose stable - fn choose_stable_new_version(mut self, rng: &mut R) -> Option - where R: Rng + ?Sized { + where + R: Rng + ?Sized, + { let mut consumed = 0; let mut result = None; let mut coin_flipper = CoinFlipper::new(rng); @@ -488,12 +405,11 @@ pub trait IteratorRandom: Iterator + Sized { // This should be replaced by `advance_by()` once it is stable: // https://github.com/rust-lang/rust/issues/77404 let mut next = 0; - let (lower, _) = self.size_hint(); if lower >= 2 { let highest_selected = (0..lower) - .filter(|ix| coin_flipper.gen_ratio(1, consumed+ix+1)) + .filter(|ix| coin_flipper.gen_ratio(1, consumed + ix + 1)) .last(); consumed += lower; @@ -508,10 +424,10 @@ pub trait IteratorRandom: Iterator + Sized { let elem = self.nth(next); if elem.is_none() { - return result + return result; } - if coin_flipper.gen_ratio(1, consumed+1) { + if coin_flipper.gen_ratio(1, consumed + 1) { result = elem; } consumed += 1; @@ -532,7 +448,9 @@ pub trait IteratorRandom: Iterator + Sized { /// Complexity is `O(n)` where `n` is the length of the iterator. /// For slices, prefer [`SliceRandom::choose_multiple`]. fn choose_multiple_fill(mut self, rng: &mut R, buf: &mut [Self::Item]) -> usize - where R: Rng + ?Sized { + where + R: Rng + ?Sized, + { let amount = buf.len(); let mut len = 0; while len < amount { @@ -572,7 +490,9 @@ pub trait IteratorRandom: Iterator + Sized { #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] fn choose_multiple(mut self, rng: &mut R, amount: usize) -> Vec - where R: Rng + ?Sized { + where + R: Rng + ?Sized, + { let mut reservoir = Vec::with_capacity(amount); reservoir.extend(self.by_ref().take(amount)); @@ -596,12 +516,13 @@ pub trait IteratorRandom: Iterator + Sized { } } - impl SliceRandom for [T] { type Item = T; fn choose(&self, rng: &mut R) -> Option<&Self::Item> - where R: Rng + ?Sized { + where + R: Rng + ?Sized, + { if self.is_empty() { None } else { @@ -610,7 +531,9 @@ impl SliceRandom for [T] { } fn choose_mut(&mut self, rng: &mut R) -> Option<&mut Self::Item> - where R: Rng + ?Sized { + where + R: Rng + ?Sized, + { if self.is_empty() { None } else { @@ -621,7 +544,9 @@ impl SliceRandom for [T] { #[cfg(feature = "alloc")] fn choose_multiple(&self, rng: &mut R, amount: usize) -> SliceChooseIter - where R: Rng + ?Sized { + where + R: Rng + ?Sized, + { let amount = ::core::cmp::min(amount, self.len()); SliceChooseIter { slice: self, @@ -692,7 +617,9 @@ impl SliceRandom for [T] { } fn shuffle(&mut self, rng: &mut R) - where R: Rng + ?Sized { + where + R: Rng + ?Sized, + { for i in (1..self.len()).rev() { // invariant: elements with index > i have been locked in place. self.swap(i, gen_index(rng, i + 1)); @@ -702,7 +629,9 @@ impl SliceRandom for [T] { fn partial_shuffle( &mut self, rng: &mut R, amount: usize, ) -> (&mut [Self::Item], &mut [Self::Item]) - where R: Rng + ?Sized { + where + R: Rng + ?Sized, + { // This applies Durstenfeld's algorithm for the // [Fisher–Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm) // for an unbiased permutation, but exits early after choosing `amount` @@ -722,7 +651,6 @@ impl SliceRandom for [T] { impl IteratorRandom for I where I: Iterator + Sized {} - /// An iterator over multiple slice elements. /// /// This struct is created by @@ -759,7 +687,6 @@ impl<'a, S: Index + ?Sized + 'a, T: 'a> ExactSizeIterator } } - // Sample a number uniformly between 0 and `ubound`. Uses 32-bit sampling where // possible, primarily in order to produce the same output on 32-bit and 64-bit // platforms. @@ -772,12 +699,13 @@ fn gen_index(rng: &mut R, ubound: usize) -> usize { } } - #[cfg(test)] mod test { use super::*; - #[cfg(feature = "alloc")] use crate::Rng; - #[cfg(all(feature = "alloc", not(feature = "std")))] use alloc::vec::Vec; + #[cfg(feature = "alloc")] + use crate::Rng; + #[cfg(all(feature = "alloc", not(feature = "std")))] + use alloc::vec::Vec; #[test] fn test_slice_choose() { @@ -938,82 +866,40 @@ mod test { #[cfg(feature = "alloc")] test_iter(r, (0..9).collect::>().into_iter()); test_iter(r, UnhintedIterator { iter: 0..9 }); - test_iter(r, ChunkHintedIterator { - iter: 0..9, - chunk_size: 4, - chunk_remaining: 4, - hint_total_size: false, - }); - test_iter(r, ChunkHintedIterator { - iter: 0..9, - chunk_size: 4, - chunk_remaining: 4, - hint_total_size: true, - }); - test_iter(r, WindowHintedIterator { - iter: 0..9, - window_size: 2, - hint_total_size: false, - }); - test_iter(r, WindowHintedIterator { - iter: 0..9, - window_size: 2, - hint_total_size: true, - }); - - assert_eq!((0..0).choose(r), None); - assert_eq!(UnhintedIterator { iter: 0..0 }.choose(r), None); - } - - #[test] - #[cfg_attr(miri, ignore)] // Miri is too slow - fn test_iterator_choose_new_version() { - let r = &mut crate::test::rng(109); - fn test_iter + Clone>(r: &mut R, iter: Iter) { - let mut chosen = [0i32; 9]; - for _ in 0..1000 { - let picked = iter.clone().choose_new_version(r).unwrap(); - chosen[picked] += 1; - } - for count in chosen.iter() { - // Samples should follow Binomial(1000, 1/9) - // Octave: binopdf(x, 1000, 1/9) gives the prob of *count == x - // Note: have seen 153, which is unlikely but not impossible. - assert!( - 72 < *count && *count < 154, - "count not close to 1000/9: {}", - count - ); - } - } - - test_iter(r, 0..9); - test_iter(r, [0, 1, 2, 3, 4, 5, 6, 7, 8].iter().cloned()); - #[cfg(feature = "alloc")] - test_iter(r, (0..9).collect::>().into_iter()); - test_iter(r, UnhintedIterator { iter: 0..9 }); - test_iter(r, ChunkHintedIterator { - iter: 0..9, - chunk_size: 4, - chunk_remaining: 4, - hint_total_size: false, - }); - test_iter(r, ChunkHintedIterator { - iter: 0..9, - chunk_size: 4, - chunk_remaining: 4, - hint_total_size: true, - }); - test_iter(r, WindowHintedIterator { - iter: 0..9, - window_size: 2, - hint_total_size: false, - }); - test_iter(r, WindowHintedIterator { - iter: 0..9, - window_size: 2, - hint_total_size: true, - }); + test_iter( + r, + ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: false, + }, + ); + test_iter( + r, + ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: true, + }, + ); + test_iter( + r, + WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: false, + }, + ); + test_iter( + r, + WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: true, + }, + ); assert_eq!((0..0).choose(r), None); assert_eq!(UnhintedIterator { iter: 0..0 }.choose(r), None); @@ -1046,82 +932,40 @@ mod test { #[cfg(feature = "alloc")] test_iter(r, (0..9).collect::>().into_iter()); test_iter(r, UnhintedIterator { iter: 0..9 }); - test_iter(r, ChunkHintedIterator { - iter: 0..9, - chunk_size: 4, - chunk_remaining: 4, - hint_total_size: false, - }); - test_iter(r, ChunkHintedIterator { - iter: 0..9, - chunk_size: 4, - chunk_remaining: 4, - hint_total_size: true, - }); - test_iter(r, WindowHintedIterator { - iter: 0..9, - window_size: 2, - hint_total_size: false, - }); - test_iter(r, WindowHintedIterator { - iter: 0..9, - window_size: 2, - hint_total_size: true, - }); - - assert_eq!((0..0).choose(r), None); - assert_eq!(UnhintedIterator { iter: 0..0 }.choose(r), None); - } - - #[test] - #[cfg_attr(miri, ignore)] // Miri is too slow - fn test_iterator_choose_stable_new_version() { - let r = &mut crate::test::rng(109); - fn test_iter + Clone>(r: &mut R, iter: Iter) { - let mut chosen = [0i32; 9]; - for _ in 0..1000 { - let picked = iter.clone().choose_stable_new_version(r).unwrap(); - chosen[picked] += 1; - } - for count in chosen.iter() { - // Samples should follow Binomial(1000, 1/9) - // Octave: binopdf(x, 1000, 1/9) gives the prob of *count == x - // Note: have seen 153, which is unlikely but not impossible. - assert!( - 72 < *count && *count < 154, - "count not close to 1000/9: {}", - count - ); - } - } - - test_iter(r, 0..9); - test_iter(r, [0, 1, 2, 3, 4, 5, 6, 7, 8].iter().cloned()); - #[cfg(feature = "alloc")] - test_iter(r, (0..9).collect::>().into_iter()); - test_iter(r, UnhintedIterator { iter: 0..9 }); - test_iter(r, ChunkHintedIterator { - iter: 0..9, - chunk_size: 4, - chunk_remaining: 4, - hint_total_size: false, - }); - test_iter(r, ChunkHintedIterator { - iter: 0..9, - chunk_size: 4, - chunk_remaining: 4, - hint_total_size: true, - }); - test_iter(r, WindowHintedIterator { - iter: 0..9, - window_size: 2, - hint_total_size: false, - }); - test_iter(r, WindowHintedIterator { - iter: 0..9, - window_size: 2, - hint_total_size: true, - }); + test_iter( + r, + ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: false, + }, + ); + test_iter( + r, + ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: true, + }, + ); + test_iter( + r, + WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: false, + }, + ); + test_iter( + r, + WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: true, + }, + ); assert_eq!((0..0).choose(r), None); assert_eq!(UnhintedIterator { iter: 0..0 }.choose(r), None); @@ -1141,76 +985,48 @@ mod test { } let reference = test_iter(0..9); - assert_eq!(test_iter([0, 1, 2, 3, 4, 5, 6, 7, 8].iter().cloned()), reference); - - #[cfg(feature = "alloc")] - assert_eq!(test_iter((0..9).collect::>().into_iter()), reference); - assert_eq!(test_iter(UnhintedIterator { iter: 0..9 }), reference); - assert_eq!(test_iter(ChunkHintedIterator { - iter: 0..9, - chunk_size: 4, - chunk_remaining: 4, - hint_total_size: false, - }), reference); - assert_eq!(test_iter(ChunkHintedIterator { - iter: 0..9, - chunk_size: 4, - chunk_remaining: 4, - hint_total_size: true, - }), reference); - assert_eq!(test_iter(WindowHintedIterator { - iter: 0..9, - window_size: 2, - hint_total_size: false, - }), reference); - assert_eq!(test_iter(WindowHintedIterator { - iter: 0..9, - window_size: 2, - hint_total_size: true, - }), reference); - } - - #[test] - #[cfg_attr(miri, ignore)] // Miri is too slow - fn test_iterator_choose_stable_stability_new_version() { - fn test_iter(iter: impl Iterator + Clone) -> [i32; 9] { - let r = &mut crate::test::rng(109); - let mut chosen = [0i32; 9]; - for _ in 0..1000 { - let picked = iter.clone().choose_stable(r).unwrap(); - chosen[picked] += 1; - } - chosen - } - - let reference = test_iter(0..9); - assert_eq!(test_iter([0, 1, 2, 3, 4, 5, 6, 7, 8].iter().cloned()), reference); + assert_eq!( + test_iter([0, 1, 2, 3, 4, 5, 6, 7, 8].iter().cloned()), + reference + ); #[cfg(feature = "alloc")] assert_eq!(test_iter((0..9).collect::>().into_iter()), reference); assert_eq!(test_iter(UnhintedIterator { iter: 0..9 }), reference); - assert_eq!(test_iter(ChunkHintedIterator { - iter: 0..9, - chunk_size: 4, - chunk_remaining: 4, - hint_total_size: false, - }), reference); - assert_eq!(test_iter(ChunkHintedIterator { - iter: 0..9, - chunk_size: 4, - chunk_remaining: 4, - hint_total_size: true, - }), reference); - assert_eq!(test_iter(WindowHintedIterator { - iter: 0..9, - window_size: 2, - hint_total_size: false, - }), reference); - assert_eq!(test_iter(WindowHintedIterator { - iter: 0..9, - window_size: 2, - hint_total_size: true, - }), reference); + assert_eq!( + test_iter(ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: false, + }), + reference + ); + assert_eq!( + test_iter(ChunkHintedIterator { + iter: 0..9, + chunk_size: 4, + chunk_remaining: 4, + hint_total_size: true, + }), + reference + ); + assert_eq!( + test_iter(WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: false, + }), + reference + ); + assert_eq!( + test_iter(WindowHintedIterator { + iter: 0..9, + window_size: 2, + hint_total_size: true, + }), + reference + ); } #[test] @@ -1381,7 +1197,7 @@ mod test { assert_eq!(choose([].iter().cloned()), None); assert_eq!(choose(0..100), Some(33)); - assert_eq!(choose(UnhintedIterator { iter: 0..100 }), Some(40)); + assert_eq!(choose(UnhintedIterator { iter: 0..100 }), Some(27)); assert_eq!( choose(ChunkHintedIterator { iter: 0..100, @@ -1426,8 +1242,8 @@ mod test { } assert_eq!(choose([].iter().cloned()), None); - assert_eq!(choose(0..100), Some(40)); - assert_eq!(choose(UnhintedIterator { iter: 0..100 }), Some(40)); + assert_eq!(choose(0..100), Some(27)); + assert_eq!(choose(UnhintedIterator { iter: 0..100 }), Some(27)); assert_eq!( choose(ChunkHintedIterator { iter: 0..100, @@ -1435,7 +1251,7 @@ mod test { chunk_remaining: 32, hint_total_size: false, }), - Some(40) + Some(27) ); assert_eq!( choose(ChunkHintedIterator { @@ -1444,7 +1260,7 @@ mod test { chunk_remaining: 32, hint_total_size: true, }), - Some(40) + Some(27) ); assert_eq!( choose(WindowHintedIterator { @@ -1452,7 +1268,7 @@ mod test { window_size: 32, hint_total_size: false, }), - Some(40) + Some(27) ); assert_eq!( choose(WindowHintedIterator { @@ -1460,7 +1276,7 @@ mod test { window_size: 32, hint_total_size: true, }), - Some(40) + Some(27) ); } @@ -1512,9 +1328,13 @@ mod test { // Case 2: All of the weights are 0 let choices = [('a', 0), ('b', 0), ('c', 0)]; - assert_eq!(choices - .choose_multiple_weighted(&mut rng, 2, |item| item.1) - .unwrap().count(), 2); + assert_eq!( + choices + .choose_multiple_weighted(&mut rng, 2, |item| item.1) + .unwrap() + .count(), + 2 + ); // Case 3: Negative weights let choices = [('a', -1), ('b', 1), ('c', 1)]; @@ -1527,9 +1347,13 @@ mod test { // Case 4: Empty list let choices = []; - assert_eq!(choices - .choose_multiple_weighted(&mut rng, 0, |_: &()| 0) - .unwrap().count(), 0); + assert_eq!( + choices + .choose_multiple_weighted(&mut rng, 0, |_: &()| 0) + .unwrap() + .count(), + 0 + ); // Case 5: NaN weights let choices = [('a', core::f64::NAN), ('b', 1.0), ('c', 1.0)]; From 79f695381c664ecc9ad2d6b3ace9ec1041c1a742 Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Mon, 21 Nov 2022 12:43:19 +0000 Subject: [PATCH 09/26] Moved sequence choose benchmarks to their own file --- benches/seq.rs | 601 +----------------------------------------- benches/seq_choose.rs | 356 +++++++++++++++++++++++++ 2 files changed, 357 insertions(+), 600 deletions(-) create mode 100644 benches/seq_choose.rs diff --git a/benches/seq.rs b/benches/seq.rs index 1d30d70d1c..3d57d4872e 100644 --- a/benches/seq.rs +++ b/benches/seq.rs @@ -13,13 +13,12 @@ extern crate test; use test::Bencher; +use core::mem::size_of; use rand::prelude::*; use rand::seq::*; -use core::mem::size_of; // We force use of 32-bit RNG since seq code is optimised for use with 32-bit // generators on all platforms. -use rand_chacha::ChaCha20Rng as CryptoRng; use rand_pcg::Pcg32 as SmallRng; const RAND_BENCH_N: u64 = 1000; @@ -75,604 +74,6 @@ seq_slice_choose_multiple!(seq_slice_choose_multiple_950_of_1000, 950, 1000); seq_slice_choose_multiple!(seq_slice_choose_multiple_10_of_100, 10, 100); seq_slice_choose_multiple!(seq_slice_choose_multiple_90_of_100, 90, 100); -#[bench] -fn seq_iter_choose_from_1000(b: &mut Bencher) { - let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); - let x: &mut [usize] = &mut [1; 1000]; - for (i, r) in x.iter_mut().enumerate() { - *r = i; - } - b.iter(|| { - let mut s = 0; - for _ in 0..RAND_BENCH_N { - s += x.iter().choose(&mut rng).unwrap(); - } - s - }); - b.bytes = size_of::() as u64 * crate::RAND_BENCH_N; -} - -#[derive(Clone)] -struct UnhintedIterator { - iter: I, -} -impl Iterator for UnhintedIterator { - type Item = I::Item; - - fn next(&mut self) -> Option { - self.iter.next() - } -} - -#[derive(Clone)] -struct WindowHintedIterator { - iter: I, - window_size: usize, -} -impl Iterator for WindowHintedIterator { - type Item = I::Item; - - fn next(&mut self) -> Option { - self.iter.next() - } - - fn size_hint(&self) -> (usize, Option) { - (core::cmp::min(self.iter.len(), self.window_size), None) - } -} - -macro_rules! bench_seq_iter_size_hinted { - ($name:ident, $rng:ident, $fn:ident, $length:expr) => { - #[bench] - fn $name(b: &mut Bencher) { - let mut rng = $rng::from_rng(thread_rng()).unwrap(); - let x: &mut [usize] = &mut [1; $length]; - for (i, r) in x.iter_mut().enumerate() { - *r = i; - } - b.iter(|| { - let mut s = 0; - for _ in 0..RAND_BENCH_N { - s += x.iter().$fn(&mut rng).unwrap(); - } - s - }); - b.bytes = size_of::() as u64 * crate::RAND_BENCH_N; - } - }; -} - -macro_rules! bench_seq_iter_unhinted { - ($name:ident,$rng:ident, $fn:ident, $length:expr) => { - #[bench] - fn $name(b: &mut Bencher) { - let mut rng = $rng::from_rng(thread_rng()).unwrap(); - let x: &[usize] = &[1; $length]; - b.iter(|| UnhintedIterator { iter: x.iter() }.$fn(&mut rng).unwrap()) - } - }; -} - -macro_rules! bench_seq_iter_window_hinted { - ($name:ident,$rng:ident, $fn:ident, $length:expr) => { - #[bench] - fn $name(b: &mut Bencher) { - let mut rng = $rng::from_rng(thread_rng()).unwrap(); - let x: &[usize] = &[1; $length]; - b.iter(|| { - WindowHintedIterator { - iter: x.iter(), - window_size: 7, - } - .$fn(&mut rng) - .unwrap() - }) - } - }; -} - -//Size Hinted - -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_10000_cryptoRng_old_version, - CryptoRng, - choose, - 10000 -); -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_10000_cryptoRng_new_version, - CryptoRng, - choose_new_version, - 10000 -); -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_10000_smallRng_old_version, - SmallRng, - choose, - 10000 -); -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_10000_smallRng_new_version, - SmallRng, - choose_new_version, - 10000 -); -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_1000_cryptoRng_old_version, - CryptoRng, - choose, - 1000 -); -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_1000_cryptoRng_new_version, - CryptoRng, - choose_new_version, - 1000 -); -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_1000_smallRng_old_version, - SmallRng, - choose, - 1000 -); -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_1000_smallRng_new_version, - SmallRng, - choose_new_version, - 1000 -); - -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_100_cryptoRng_old_version, - CryptoRng, - choose, - 100 -); -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_100_cryptoRng_new_version, - CryptoRng, - choose_new_version, - 100 -); -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_100_smallRng_old_version, - SmallRng, - choose, - 100 -); -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_100_smallRng_new_version, - SmallRng, - choose_new_version, - 100 -); -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_10_smallRng_old_version, - SmallRng, - choose, - 10 -); -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_10_smallRng_new_version, - SmallRng, - choose_new_version, - 10 -); - -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_10_cryptoRng_old_version, - CryptoRng, - choose, - 10 -); -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_10_cryptoRng_new_version, - CryptoRng, - choose_new_version, - 10 -); - -//Unhinted - -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_10000_cryptoRng_old_version, - CryptoRng, - choose, - 10000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_10000_cryptoRng_new_version, - CryptoRng, - choose_new_version, - 10000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_10000_smallRng_old_version, - SmallRng, - choose, - 10000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_10000_smallRng_new_version, - SmallRng, - choose_new_version, - 10000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_1000_cryptoRng_old_version, - CryptoRng, - choose, - 1000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_1000_cryptoRng_new_version, - CryptoRng, - choose_new_version, - 1000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_1000_smallRng_old_version, - SmallRng, - choose, - 1000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_1000_smallRng_new_version, - SmallRng, - choose_new_version, - 1000 -); - -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_100_cryptoRng_old_version, - CryptoRng, - choose, - 100 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_100_cryptoRng_new_version, - CryptoRng, - choose_new_version, - 100 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_100_smallRng_old_version, - SmallRng, - choose, - 100 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_100_smallRng_new_version, - SmallRng, - choose_new_version, - 100 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_10_smallRng_old_version, - SmallRng, - choose, - 10 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_10_smallRng_new_version, - SmallRng, - choose_new_version, - 10 -); - -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_10_cryptoRng_old_version, - CryptoRng, - choose, - 10 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_10_cryptoRng_new_version, - CryptoRng, - choose_new_version, - 10 -); - -// Window hinted - -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_10000_cryptoRng_old_version, - CryptoRng, - choose, - 10000 -); -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_10000_cryptoRng_new_version, - CryptoRng, - choose_new_version, - 10000 -); -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_10000_smallRng_old_version, - SmallRng, - choose, - 10000 -); -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_10000_smallRng_new_version, - SmallRng, - choose_new_version, - 10000 -); -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_1000_cryptoRng_old_version, - CryptoRng, - choose, - 1000 -); -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_1000_cryptoRng_new_version, - CryptoRng, - choose_new_version, - 1000 -); -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_1000_smallRng_old_version, - SmallRng, - choose, - 1000 -); -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_1000_smallRng_new_version, - SmallRng, - choose_new_version, - 1000 -); - -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_100_cryptoRng_old_version, - CryptoRng, - choose, - 100 -); -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_100_cryptoRng_new_version, - CryptoRng, - choose_new_version, - 100 -); -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_100_smallRng_old_version, - SmallRng, - choose, - 100 -); -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_100_smallRng_new_version, - SmallRng, - choose_new_version, - 100 -); -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_10_smallRng_old_version, - SmallRng, - choose, - 10 -); -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_10_smallRng_new_version, - SmallRng, - choose_new_version, - 10 -); -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_10_cryptoRng_old_version, - CryptoRng, - choose, - 10 -); -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_10_cryptoRng_new_version, - CryptoRng, - choose_new_version, - 10 -); - -//Choose Stable -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_10000_cryptoRng_old_version, - CryptoRng, - choose_stable, - 10000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_10000_cryptoRng_new_version, - CryptoRng, - choose_stable_new_version, - 10000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_10000_smallRng_old_version, - SmallRng, - choose_stable, - 10000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_10000_smallRng_new_version, - SmallRng, - choose_stable_new_version, - 10000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_1000_cryptoRng_old_version, - CryptoRng, - choose_stable, - 1000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_1000_cryptoRng_new_version, - CryptoRng, - choose_stable_new_version, - 1000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_1000_smallRng_old_version, - SmallRng, - choose_stable, - 1000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_1000_smallRng_new_version, - SmallRng, - choose_stable_new_version, - 1000 -); - -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_100_cryptoRng_old_version, - CryptoRng, - choose_stable, - 100 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_100_cryptoRng_new_version, - CryptoRng, - choose_stable_new_version, - 100 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_100_smallRng_old_version, - SmallRng, - choose_stable, - 100 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_100_smallRng_new_version, - SmallRng, - choose_stable_new_version, - 100 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_10_smallRng_old_version, - SmallRng, - choose_stable, - 10 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_10_smallRng_new_version, - SmallRng, - choose_stable_new_version, - 10 -); - -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_10_cryptoRng_old_version, - CryptoRng, - choose_stable, - 10 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_10_cryptoRng_new_version, - CryptoRng, - choose_stable_new_version, - 10 -); - -// Window hinted - -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_10000_cryptoRng_old_version, - CryptoRng, - choose_stable, - 10000 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_10000_cryptoRng_new_version, - CryptoRng, - choose_stable_new_version, - 10000 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_10000_smallRng_old_version, - SmallRng, - choose_stable, - 10000 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_10000_smallRng_new_version, - SmallRng, - choose_stable_new_version, - 10000 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_1000_cryptoRng_old_version, - CryptoRng, - choose_stable, - 1000 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_1000_cryptoRng_new_version, - CryptoRng, - choose_stable_new_version, - 1000 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_1000_smallRng_old_version, - SmallRng, - choose_stable, - 1000 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_1000_smallRng_new_version, - SmallRng, - choose_stable_new_version, - 1000 -); - -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_100_cryptoRng_old_version, - CryptoRng, - choose_stable, - 100 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_100_cryptoRng_new_version, - CryptoRng, - choose_stable_new_version, - 100 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_100_smallRng_old_version, - SmallRng, - choose_stable, - 100 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_100_smallRng_new_version, - SmallRng, - choose_stable_new_version, - 100 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_10_smallRng_old_version, - SmallRng, - choose_stable, - 10 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_10_smallRng_new_version, - SmallRng, - choose_stable_new_version, - 10 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_10_cryptoRng_old_version, - CryptoRng, - choose_stable, - 10 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_10_cryptoRng_new_version, - CryptoRng, - choose_stable_new_version, - 10 -); - #[bench] fn seq_iter_choose_multiple_10_of_100(b: &mut Bencher) { let mut rng = SmallRng::from_rng(thread_rng()).unwrap(); diff --git a/benches/seq_choose.rs b/benches/seq_choose.rs new file mode 100644 index 0000000000..a0d8244c9c --- /dev/null +++ b/benches/seq_choose.rs @@ -0,0 +1,356 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![feature(test)] +#![allow(non_snake_case)] + +extern crate test; + +use test::Bencher; + +use rand::prelude::*; +use core::mem::size_of; + +// We force use of 32-bit RNG since seq code is optimised for use with 32-bit +// generators on all platforms. +use rand_chacha::ChaCha20Rng as CryptoRng; +use rand_pcg::Pcg32 as SmallRng; + +const RAND_BENCH_N: u64 = 1000; + + +#[derive(Clone)] +struct UnhintedIterator { + iter: I, +} +impl Iterator for UnhintedIterator { + type Item = I::Item; + + fn next(&mut self) -> Option { + self.iter.next() + } +} + +#[derive(Clone)] +struct WindowHintedIterator { + iter: I, + window_size: usize, +} +impl Iterator for WindowHintedIterator { + type Item = I::Item; + + fn next(&mut self) -> Option { + self.iter.next() + } + + fn size_hint(&self) -> (usize, Option) { + (core::cmp::min(self.iter.len(), self.window_size), None) + } +} + +macro_rules! bench_seq_iter_size_hinted { + ($name:ident, $rng:ident, $fn:ident, $length:expr) => { + #[bench] + fn $name(b: &mut Bencher) { + let mut rng = $rng::from_rng(thread_rng()).unwrap(); + let x: &mut [usize] = &mut [1; $length]; + for (i, r) in x.iter_mut().enumerate() { + *r = i; + } + b.iter(|| { + let mut s = 0; + for _ in 0..RAND_BENCH_N { + s += x.iter().$fn(&mut rng).unwrap(); + } + s + }); + b.bytes = size_of::() as u64 * crate::RAND_BENCH_N; + } + }; +} + +macro_rules! bench_seq_iter_unhinted { + ($name:ident,$rng:ident, $fn:ident, $length:expr) => { + #[bench] + fn $name(b: &mut Bencher) { + let mut rng = $rng::from_rng(thread_rng()).unwrap(); + let x: &[usize] = &[1; $length]; + b.iter(|| UnhintedIterator { iter: x.iter() }.$fn(&mut rng).unwrap()) + } + }; +} + +macro_rules! bench_seq_iter_window_hinted { + ($name:ident,$rng:ident, $fn:ident, $length:expr) => { + #[bench] + fn $name(b: &mut Bencher) { + let mut rng = $rng::from_rng(thread_rng()).unwrap(); + let x: &[usize] = &[1; $length]; + b.iter(|| { + WindowHintedIterator { + iter: x.iter(), + window_size: 7, + } + .$fn(&mut rng) + .unwrap() + }) + } + }; +} + +//Size Hinted +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_10000_cryptoRng, + CryptoRng, + choose, + 10000 +); +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_10000_smallRng, + SmallRng, + choose, + 10000 +); +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_1000_cryptoRng, + CryptoRng, + choose, + 1000 +); +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_1000_smallRng, + SmallRng, + choose, + 1000 +); +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_100_cryptoRng, + CryptoRng, + choose, + 100 +); +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_100_smallRng, + SmallRng, + choose, + 100 +); +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_10_smallRng, + SmallRng, + choose, + 10 +); +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_10_cryptoRng, + CryptoRng, + choose, + 10 +); + +//Unhinted +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_10000_cryptoRng, + CryptoRng, + choose, + 10000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_10000_smallRng, + SmallRng, + choose, + 10000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_1000_cryptoRng, + CryptoRng, + choose, + 1000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_1000_smallRng, + SmallRng, + choose, + 1000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_100_cryptoRng, + CryptoRng, + choose, + 100 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_100_smallRng, + SmallRng, + choose, + 100 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_10_smallRng, + SmallRng, + choose, + 10 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_10_cryptoRng, + CryptoRng, + choose, + 10 +); + +// Window hinted +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_10000_cryptoRng, + CryptoRng, + choose, + 10000 +); +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_10000_smallRng, + SmallRng, + choose, + 10000 +); +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_1000_cryptoRng, + CryptoRng, + choose, + 1000 +); +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_1000_smallRng, + SmallRng, + choose, + 1000 +); + +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_100_cryptoRng, + CryptoRng, + choose, + 100 +); +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_100_smallRng, + SmallRng, + choose, + 100 +); +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_10_smallRng, + SmallRng, + choose, + 10 +); +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_10_cryptoRng, + CryptoRng, + choose, + 10 +); + +//Choose Stable +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_10000_cryptoRng, + CryptoRng, + choose_stable, + 10000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_10000_smallRng, + SmallRng, + choose_stable, + 10000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_1000_cryptoRng, + CryptoRng, + choose_stable, + 1000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_1000_smallRng, + SmallRng, + choose_stable, + 1000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_100_cryptoRng, + CryptoRng, + choose_stable, + 100 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_100_smallRng, + SmallRng, + choose_stable, + 100 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_10_smallRng, + SmallRng, + choose_stable, + 10 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_10_cryptoRng, + CryptoRng, + choose_stable, + 10 +); + +// Window hinted +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_10000_cryptoRng, + CryptoRng, + choose_stable, + 10000 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_10000_smallRng, + SmallRng, + choose_stable, + 10000 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_1000_cryptoRng, + CryptoRng, + choose_stable, + 1000 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_1000_smallRng, + SmallRng, + choose_stable, + 1000 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_100_cryptoRng, + CryptoRng, + choose_stable, + 100 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_100_smallRng, + SmallRng, + choose_stable, + 100 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_10_smallRng, + SmallRng, + choose_stable, + 10 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_10_cryptoRng, + CryptoRng, + choose_stable, + 10 +); + From b5312f430181746aa9f163d5cd6b04482eb6ade4 Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Mon, 5 Dec 2022 13:07:24 +0000 Subject: [PATCH 10/26] Reworked coin_flipper --- benches/seq_choose.rs | 42 ++++++++++++++ src/seq/coin_flipper.rs | 118 ++++++++++++++++++++++++++-------------- src/seq/mod.rs | 6 +- 3 files changed, 121 insertions(+), 45 deletions(-) diff --git a/benches/seq_choose.rs b/benches/seq_choose.rs index a0d8244c9c..8704702a41 100644 --- a/benches/seq_choose.rs +++ b/benches/seq_choose.rs @@ -203,6 +203,48 @@ bench_seq_iter_unhinted!( 10 ); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_4_smallRng, + SmallRng, + choose, + 4 +); + +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_2_smallRng, + SmallRng, + choose, + 2 +); + +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_1_smallRng, + SmallRng, + choose, + 1 +); + +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_4_cryptoRng, + CryptoRng, + choose, + 4 +); + +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_2_cryptoRng, + CryptoRng, + choose, + 2 +); + +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_1_cryptoRng, + CryptoRng, + choose, + 1 +); + // Window hinted bench_seq_iter_window_hinted!( seq_iter_window_hinted_choose_from_10000_cryptoRng, diff --git a/src/seq/coin_flipper.rs b/src/seq/coin_flipper.rs index 5465f5ead2..84d6a67315 100644 --- a/src/seq/coin_flipper.rs +++ b/src/seq/coin_flipper.rs @@ -16,10 +16,27 @@ impl CoinFlipper { } #[inline] - /// Returns true with a probability of numerator / denominator + /// Returns true with a probability of 1 / d /// Uses an expected two bits of randomness - pub fn gen_ratio(&mut self, mut numerator: usize, denominator: usize) -> bool { - // Explanation: + pub fn gen_ratio_one_over(&mut self, d: usize) -> bool { + // This uses the same logic as `gen_ratio` but is optimized for the case that the starting numerator is one (which it always is for `Sequence::Choose()`) + + // In this case (unlike in `gen_ratio`), this way of calculating c is always accurate + let c = (usize::BITS - 1 - d.leading_zeros()).min(32); + + if self.flip_until_tails(c) { + let numerator = 1 << c; + return self.gen_ratio(numerator, d); + } else { + return false; + } + } + + #[inline] + /// Returns true with a probability of n / d + /// Uses an expected two bits of randomness + fn gen_ratio(&mut self, mut n: usize, d: usize) -> bool { + // Explanation: // We are trying to return true with a probability of n / d // If n >= d, we can just return true // Otherwise there are two possibilities 2n < d and 2n >= d @@ -29,57 +46,74 @@ impl CoinFlipper { // If it comes up heads, double n and start again // This is fair because (0.5 * 0) + (0.5 * 2n / d) = n / d and 2n is less than d (if 2n was greater than d we would effectively round it down to 1 by returning true) // If 2n >= d - // If it comes up tails, set n to 2n - d + // If it comes up tails, set n to 2n - d and start again // If it comes up heads, return true // This is fair because (0.5 * 1) + (0.5 * (2n - d) / d) = n / d + // Note that if 2n = d and the coin comes up tails, n will be set to 0 before restarting which is equivalent to returning false. + + // As a performance optimization we can flip multiple coins at once (using the `lzcnt` intrinsic) + // We can check up to 32 flips at once but we only receive one bit of information - all heads or at least one tail. + // c is the number of coins to flip. + // If 2n < d, c is the highest number such that n * 2^c < d and c <= 32. + // If the result is all heads, then set n to n * 2^c + // If there was at least one tail, return false + // If 2n >= d, the order of the heads and tails matters so we flip one coin at a time - c = 1 + + while n < d { + //Estimate c by counting leading zeros. This will either give the correct c, or c + 1 + let mut c = (n.leading_zeros() - d.leading_zeros()).min(32).max(1); - while numerator < denominator { - //The exponent is the number of heads that need to be won in a row to not return false - let exponent = (numerator.leading_zeros() - denominator.leading_zeros()).min(32); - if exponent > 1 { - //n * 2^exponent < d - if self.flip_until_tails(exponent) { - // all heads - numerator <<= exponent; + // set next_n to n * 2^c (checked_shl will fail if 2n >= `usize::max`) + if let Some(mut next_n) = n.checked_shl(c) { + + // Check here that our estimate for c was correct, if not, lower it by one + if next_n > d && c > 1 { + next_n >>= 1; + c -= 1; + } + + if self.flip_until_tails(c) { + //All heads + //if 2n < d, set n to 2n + //if 2n >= d, the while loop will exit and we will return `true` + n = next_n } else { - // a tails somewhere - return false; + //At least one tail - either return false or set n to 2n-d + n = next_n.saturating_sub(d); + + if n == 0 { + //Because we used saturating_sub, n will be zero if 2n was less than d or 2n was equal to d, in either case we can return false. + return false; + } } - } - //Otherwise n * 2 is either greater than or very close to d - else if self.flip_until_tails(1) { - // Heads - double n. - // If it is now larger than d the loop will exit a - numerator = numerator.saturating_mul(2); } else { - // Tails - // If 2n < d return false - // Else set n to 2n -d - - numerator = numerator.wrapping_sub(denominator).wrapping_add(numerator); - if numerator > denominator { - //This is equivalent to checking if 2n < d (hence the wrapping) - return false; + // This branch will only be reached when 2n >= `usize::max` + // Obviously 2n > d + if self.flip_until_tails(1) { + //heads + return true; + } else { + //tails + n = n.saturating_add(n).saturating_sub(d); // set n to 2n -d } } } true } - - #[inline] - /// If the next n bits of randomness are all zeroes, consume them and return true. + + /// If the next `c` bits of randomness are all zeroes, consume them and return true. /// Otherwise return false and consume the number of zeroes plus one /// Generates new bits of randomness when necessary (int 32 bit chunks) - /// Has a one in 2 to the n chance of returning true - /// n must be less than or equal to 32 - fn flip_until_tails(&mut self, mut n: u32) -> bool { - debug_assert!(n <= 32); //If n > 32 this wil always return false - //Note that zeros on the left of the chunk represent heads. It needs to be this way round because zeros are filled in when left shifting + /// Has a one in 2 to the `c` chance of returning true + /// `c` must be less than or equal to 32 + fn flip_until_tails(&mut self, mut c: u32) -> bool { + debug_assert!(c <= 32); //If `c` > 32 this wil always return false + //Note that zeros on the left of the chunk represent heads. It needs to be this way round because zeros are filled in when left shifting loop { let zeros = self.chunk.leading_zeros(); - if zeros < n { + if zeros < c { // The happy path - we found a 1 and can return false // Note that because a 1 bit was detected, we cannot have run out of random bits so we don't need to check @@ -88,16 +122,16 @@ impl CoinFlipper { self.chunk_remaining = self.chunk_remaining.saturating_sub(zeros + 1); return false; } else { - // The number of zeros is larger than n + // The number of zeros is larger than `c` //There are two possibilities - if let Some(new_remaining) = self.chunk_remaining.checked_sub(n) { - //Those zeroes were all part of our random chunk, so throw away n bits of randomness and return true + if let Some(new_remaining) = self.chunk_remaining.checked_sub(c) { + //Those zeroes were all part of our random chunk, so throw away `c` bits of randomness and return true self.chunk_remaining = new_remaining; - self.chunk <<= n; + self.chunk <<= c; return true; } else { // Some of those zeroes were part of the random chunk and some were part of the space behind it - n -= self.chunk_remaining; //Take into account the zeroes that were random + c -= self.chunk_remaining; //Take into account the zeroes that were random // Generate a new chunk self.chunk = self.rng.next_u32(); diff --git a/src/seq/mod.rs b/src/seq/mod.rs index 65d6f1b9d0..85d1627467 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -362,7 +362,7 @@ pub trait IteratorRandom: Iterator + Sized { return result; } consumed += 1; - if coin_flipper.gen_ratio(1, consumed) { + if coin_flipper.gen_ratio_one_over(consumed) { result = elem; } } @@ -409,7 +409,7 @@ pub trait IteratorRandom: Iterator + Sized { let (lower, _) = self.size_hint(); if lower >= 2 { let highest_selected = (0..lower) - .filter(|ix| coin_flipper.gen_ratio(1, consumed + ix + 1)) + .filter(|ix| coin_flipper.gen_ratio_one_over(consumed + ix + 1)) .last(); consumed += lower; @@ -427,7 +427,7 @@ pub trait IteratorRandom: Iterator + Sized { return result; } - if coin_flipper.gen_ratio(1, consumed + 1) { + if coin_flipper.gen_ratio_one_over(consumed + 1) { result = elem; } consumed += 1; From 23395391370ab95694558be90686eb16494e590a Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Mon, 5 Dec 2022 17:35:11 +0000 Subject: [PATCH 11/26] Use criterion for seq_choose benches --- Cargo.toml | 8 + benches/seq_choose.rs | 444 +++++++++--------------------------------- 2 files changed, 98 insertions(+), 354 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bc39334db6..1026d7d87d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,3 +73,11 @@ rand_pcg = { path = "rand_pcg", version = "0.3.0" } # Only to test serde1 bincode = "1.2.1" rayon = "1.5.3" +criterion = { version = "0.3" } +criterion-cycles-per-byte = "0.1" + + +[[bench]] +name = "seq_choose" +path = "benches/seq_choose.rs" +harness = false \ No newline at end of file diff --git a/benches/seq_choose.rs b/benches/seq_choose.rs index 8704702a41..81048b1163 100644 --- a/benches/seq_choose.rs +++ b/benches/seq_choose.rs @@ -1,4 +1,4 @@ -// Copyright 2018 Developers of the Rand project. +// Copyright 2018-2022 Developers of the Rand project. // // Licensed under the Apache License, Version 2.0 or the MIT license @@ -6,23 +6,103 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -#![feature(test)] -#![allow(non_snake_case)] - -extern crate test; - -use test::Bencher; - +use criterion::{criterion_group, criterion_main, Criterion}; +use criterion_cycles_per_byte::CyclesPerByte; use rand::prelude::*; -use core::mem::size_of; // We force use of 32-bit RNG since seq code is optimised for use with 32-bit // generators on all platforms. use rand_chacha::ChaCha20Rng as CryptoRng; use rand_pcg::Pcg32 as SmallRng; -const RAND_BENCH_N: u64 = 1000; +criterion_group!( +name = benches; +config = Criterion::default().with_measurement(CyclesPerByte); +targets = bench +); +criterion_main!(benches); + +pub fn bench(c: &mut Criterion) { + for length in [1, 2, 3, 10, 100, 1000] { + c.bench_function(format!("choose_from_{length}_small").as_str(), |b| { + let mut rng = SmallRng::seed_from_u64(123); + b.iter(|| choose(length, &mut rng)) + }); + + c.bench_function(format!("choose_stable_from_{length}_small").as_str(), |b| { + let mut rng = SmallRng::seed_from_u64(123); + b.iter(|| choose_stable(length, &mut rng)) + }); + + c.bench_function( + format!("choose_unhinted_from_{length}_small").as_str(), + |b| { + let mut rng = SmallRng::seed_from_u64(123); + b.iter(|| choose_unhinted(length, &mut rng)) + }, + ); + + c.bench_function( + format!("choose_windowed_from_{length}_small").as_str(), + |b| { + let mut rng = SmallRng::seed_from_u64(123); + b.iter(|| choose_windowed(length, 7, &mut rng)) + }, + ); + + c.bench_function(format!("choose_from_{length}_crypto").as_str(), |b| { + let mut rng = CryptoRng::seed_from_u64(123); + b.iter(|| choose(length, &mut rng)) + }); + + c.bench_function( + format!("choose_stable_from_{length}_crypto").as_str(), + |b| { + let mut rng = CryptoRng::seed_from_u64(123); + b.iter(|| choose_stable(length, &mut rng)) + }, + ); + + c.bench_function( + format!("choose_unhinted_from_{length}_crypto").as_str(), + |b| { + let mut rng = CryptoRng::seed_from_u64(123); + b.iter(|| choose_unhinted(length, &mut rng)) + }, + ); + + c.bench_function( + format!("choose_windowed_from_{length}_crypto").as_str(), + |b| { + let mut rng = CryptoRng::seed_from_u64(123); + b.iter(|| choose_windowed(length, 7, &mut rng)) + }, + ); + } +} +fn choose(max: usize, rng: &mut R) -> Option { + let iterator = 0..max; + iterator.choose(rng) +} + +fn choose_stable(max: usize, rng: &mut R) -> Option { + let iterator = 0..max; + iterator.choose_stable(rng) +} + +fn choose_unhinted(max: usize, rng: &mut R) -> Option { + let iterator = UnhintedIterator { iter: (0..max) }; + iterator.choose(rng) +} + +fn choose_windowed(max: usize, window_size: usize, rng: &mut R) -> Option { + let iterator = WindowHintedIterator { + iter: (0..max), + window_size, + }; + iterator.choose(rng) +} #[derive(Clone)] struct UnhintedIterator { @@ -52,347 +132,3 @@ impl Iterator for WindowHintedIterator< (core::cmp::min(self.iter.len(), self.window_size), None) } } - -macro_rules! bench_seq_iter_size_hinted { - ($name:ident, $rng:ident, $fn:ident, $length:expr) => { - #[bench] - fn $name(b: &mut Bencher) { - let mut rng = $rng::from_rng(thread_rng()).unwrap(); - let x: &mut [usize] = &mut [1; $length]; - for (i, r) in x.iter_mut().enumerate() { - *r = i; - } - b.iter(|| { - let mut s = 0; - for _ in 0..RAND_BENCH_N { - s += x.iter().$fn(&mut rng).unwrap(); - } - s - }); - b.bytes = size_of::() as u64 * crate::RAND_BENCH_N; - } - }; -} - -macro_rules! bench_seq_iter_unhinted { - ($name:ident,$rng:ident, $fn:ident, $length:expr) => { - #[bench] - fn $name(b: &mut Bencher) { - let mut rng = $rng::from_rng(thread_rng()).unwrap(); - let x: &[usize] = &[1; $length]; - b.iter(|| UnhintedIterator { iter: x.iter() }.$fn(&mut rng).unwrap()) - } - }; -} - -macro_rules! bench_seq_iter_window_hinted { - ($name:ident,$rng:ident, $fn:ident, $length:expr) => { - #[bench] - fn $name(b: &mut Bencher) { - let mut rng = $rng::from_rng(thread_rng()).unwrap(); - let x: &[usize] = &[1; $length]; - b.iter(|| { - WindowHintedIterator { - iter: x.iter(), - window_size: 7, - } - .$fn(&mut rng) - .unwrap() - }) - } - }; -} - -//Size Hinted -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_10000_cryptoRng, - CryptoRng, - choose, - 10000 -); -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_10000_smallRng, - SmallRng, - choose, - 10000 -); -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_1000_cryptoRng, - CryptoRng, - choose, - 1000 -); -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_1000_smallRng, - SmallRng, - choose, - 1000 -); -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_100_cryptoRng, - CryptoRng, - choose, - 100 -); -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_100_smallRng, - SmallRng, - choose, - 100 -); -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_10_smallRng, - SmallRng, - choose, - 10 -); -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_10_cryptoRng, - CryptoRng, - choose, - 10 -); - -//Unhinted -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_10000_cryptoRng, - CryptoRng, - choose, - 10000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_10000_smallRng, - SmallRng, - choose, - 10000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_1000_cryptoRng, - CryptoRng, - choose, - 1000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_1000_smallRng, - SmallRng, - choose, - 1000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_100_cryptoRng, - CryptoRng, - choose, - 100 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_100_smallRng, - SmallRng, - choose, - 100 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_10_smallRng, - SmallRng, - choose, - 10 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_10_cryptoRng, - CryptoRng, - choose, - 10 -); - -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_4_smallRng, - SmallRng, - choose, - 4 -); - -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_2_smallRng, - SmallRng, - choose, - 2 -); - -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_1_smallRng, - SmallRng, - choose, - 1 -); - -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_4_cryptoRng, - CryptoRng, - choose, - 4 -); - -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_2_cryptoRng, - CryptoRng, - choose, - 2 -); - -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_1_cryptoRng, - CryptoRng, - choose, - 1 -); - -// Window hinted -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_10000_cryptoRng, - CryptoRng, - choose, - 10000 -); -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_10000_smallRng, - SmallRng, - choose, - 10000 -); -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_1000_cryptoRng, - CryptoRng, - choose, - 1000 -); -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_1000_smallRng, - SmallRng, - choose, - 1000 -); - -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_100_cryptoRng, - CryptoRng, - choose, - 100 -); -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_100_smallRng, - SmallRng, - choose, - 100 -); -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_10_smallRng, - SmallRng, - choose, - 10 -); -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_10_cryptoRng, - CryptoRng, - choose, - 10 -); - -//Choose Stable -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_10000_cryptoRng, - CryptoRng, - choose_stable, - 10000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_10000_smallRng, - SmallRng, - choose_stable, - 10000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_1000_cryptoRng, - CryptoRng, - choose_stable, - 1000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_1000_smallRng, - SmallRng, - choose_stable, - 1000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_100_cryptoRng, - CryptoRng, - choose_stable, - 100 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_100_smallRng, - SmallRng, - choose_stable, - 100 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_10_smallRng, - SmallRng, - choose_stable, - 10 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_10_cryptoRng, - CryptoRng, - choose_stable, - 10 -); - -// Window hinted -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_10000_cryptoRng, - CryptoRng, - choose_stable, - 10000 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_10000_smallRng, - SmallRng, - choose_stable, - 10000 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_1000_cryptoRng, - CryptoRng, - choose_stable, - 1000 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_1000_smallRng, - SmallRng, - choose_stable, - 1000 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_100_cryptoRng, - CryptoRng, - choose_stable, - 100 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_100_smallRng, - SmallRng, - choose_stable, - 100 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_10_smallRng, - SmallRng, - choose_stable, - 10 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_10_cryptoRng, - CryptoRng, - choose_stable, - 10 -); - From 309959c336dd9339f8fd545bce558a30c112e543 Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Mon, 5 Dec 2022 17:58:58 +0000 Subject: [PATCH 12/26] Removed an old comment --- src/seq/coin_flipper.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/seq/coin_flipper.rs b/src/seq/coin_flipper.rs index 84d6a67315..5a151c995b 100644 --- a/src/seq/coin_flipper.rs +++ b/src/seq/coin_flipper.rs @@ -135,8 +135,8 @@ impl CoinFlipper { // Generate a new chunk self.chunk = self.rng.next_u32(); - self.chunk_remaining = 32; //TODO change back to U32::BITS - //Go back to start of loop + self.chunk_remaining = 32; + //Go back to start of loop } } } From 2a2f4344c1f20db9954d1a63d3bdcf8947537fa3 Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Mon, 5 Dec 2022 18:33:29 +0000 Subject: [PATCH 13/26] Change how c is estimated in coin_flipper --- src/seq/coin_flipper.rs | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/seq/coin_flipper.rs b/src/seq/coin_flipper.rs index 5a151c995b..1eaf003a2c 100644 --- a/src/seq/coin_flipper.rs +++ b/src/seq/coin_flipper.rs @@ -53,26 +53,20 @@ impl CoinFlipper { // As a performance optimization we can flip multiple coins at once (using the `lzcnt` intrinsic) // We can check up to 32 flips at once but we only receive one bit of information - all heads or at least one tail. - // c is the number of coins to flip. - // If 2n < d, c is the highest number such that n * 2^c < d and c <= 32. + // Let c be the number of coins to flip. 1 <= c <= 32 + // If 2n < d, n * 2^c < d // If the result is all heads, then set n to n * 2^c // If there was at least one tail, return false - // If 2n >= d, the order of the heads and tails matters so we flip one coin at a time - c = 1 + // If 2n >= d, the order of the heads and tails matters so we flip one coin at a time so c = 1 + // Ideally, c will be as high as possible within these constraints while n < d { - //Estimate c by counting leading zeros. This will either give the correct c, or c + 1 - let mut c = (n.leading_zeros() - d.leading_zeros()).min(32).max(1); - + //Find a good value for c by counting leading zeros + //This will either give the highest possible c, or 1 less than that + let c = n.leading_zeros().saturating_sub(d.leading_zeros() + 1).min(32).max(1); // set next_n to n * 2^c (checked_shl will fail if 2n >= `usize::max`) - if let Some(mut next_n) = n.checked_shl(c) { - - // Check here that our estimate for c was correct, if not, lower it by one - if next_n > d && c > 1 { - next_n >>= 1; - c -= 1; - } - + if let Some(next_n) = n.checked_shl(c) { if self.flip_until_tails(c) { //All heads //if 2n < d, set n to 2n From b3fdc3f8c80ea05ad73ba38e6fa05dcbe285181b Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Mon, 5 Dec 2022 19:29:21 +0000 Subject: [PATCH 14/26] Revert "Use criterion for seq_choose benches" This reverts commit 23395391370ab95694558be90686eb16494e590a. --- Cargo.toml | 8 - benches/seq_choose.rs | 444 +++++++++++++++++++++++++++++++++--------- 2 files changed, 354 insertions(+), 98 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1026d7d87d..bc39334db6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,11 +73,3 @@ rand_pcg = { path = "rand_pcg", version = "0.3.0" } # Only to test serde1 bincode = "1.2.1" rayon = "1.5.3" -criterion = { version = "0.3" } -criterion-cycles-per-byte = "0.1" - - -[[bench]] -name = "seq_choose" -path = "benches/seq_choose.rs" -harness = false \ No newline at end of file diff --git a/benches/seq_choose.rs b/benches/seq_choose.rs index 81048b1163..8704702a41 100644 --- a/benches/seq_choose.rs +++ b/benches/seq_choose.rs @@ -1,4 +1,4 @@ -// Copyright 2018-2022 Developers of the Rand project. +// Copyright 2018 Developers of the Rand project. // // Licensed under the Apache License, Version 2.0 or the MIT license @@ -6,103 +6,23 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use criterion::{criterion_group, criterion_main, Criterion}; -use criterion_cycles_per_byte::CyclesPerByte; +#![feature(test)] +#![allow(non_snake_case)] + +extern crate test; + +use test::Bencher; + use rand::prelude::*; +use core::mem::size_of; // We force use of 32-bit RNG since seq code is optimised for use with 32-bit // generators on all platforms. use rand_chacha::ChaCha20Rng as CryptoRng; use rand_pcg::Pcg32 as SmallRng; -criterion_group!( -name = benches; -config = Criterion::default().with_measurement(CyclesPerByte); -targets = bench -); -criterion_main!(benches); - -pub fn bench(c: &mut Criterion) { - for length in [1, 2, 3, 10, 100, 1000] { - c.bench_function(format!("choose_from_{length}_small").as_str(), |b| { - let mut rng = SmallRng::seed_from_u64(123); - b.iter(|| choose(length, &mut rng)) - }); - - c.bench_function(format!("choose_stable_from_{length}_small").as_str(), |b| { - let mut rng = SmallRng::seed_from_u64(123); - b.iter(|| choose_stable(length, &mut rng)) - }); - - c.bench_function( - format!("choose_unhinted_from_{length}_small").as_str(), - |b| { - let mut rng = SmallRng::seed_from_u64(123); - b.iter(|| choose_unhinted(length, &mut rng)) - }, - ); - - c.bench_function( - format!("choose_windowed_from_{length}_small").as_str(), - |b| { - let mut rng = SmallRng::seed_from_u64(123); - b.iter(|| choose_windowed(length, 7, &mut rng)) - }, - ); - - c.bench_function(format!("choose_from_{length}_crypto").as_str(), |b| { - let mut rng = CryptoRng::seed_from_u64(123); - b.iter(|| choose(length, &mut rng)) - }); - - c.bench_function( - format!("choose_stable_from_{length}_crypto").as_str(), - |b| { - let mut rng = CryptoRng::seed_from_u64(123); - b.iter(|| choose_stable(length, &mut rng)) - }, - ); - - c.bench_function( - format!("choose_unhinted_from_{length}_crypto").as_str(), - |b| { - let mut rng = CryptoRng::seed_from_u64(123); - b.iter(|| choose_unhinted(length, &mut rng)) - }, - ); - - c.bench_function( - format!("choose_windowed_from_{length}_crypto").as_str(), - |b| { - let mut rng = CryptoRng::seed_from_u64(123); - b.iter(|| choose_windowed(length, 7, &mut rng)) - }, - ); - } -} +const RAND_BENCH_N: u64 = 1000; -fn choose(max: usize, rng: &mut R) -> Option { - let iterator = 0..max; - iterator.choose(rng) -} - -fn choose_stable(max: usize, rng: &mut R) -> Option { - let iterator = 0..max; - iterator.choose_stable(rng) -} - -fn choose_unhinted(max: usize, rng: &mut R) -> Option { - let iterator = UnhintedIterator { iter: (0..max) }; - iterator.choose(rng) -} - -fn choose_windowed(max: usize, window_size: usize, rng: &mut R) -> Option { - let iterator = WindowHintedIterator { - iter: (0..max), - window_size, - }; - iterator.choose(rng) -} #[derive(Clone)] struct UnhintedIterator { @@ -132,3 +52,347 @@ impl Iterator for WindowHintedIterator< (core::cmp::min(self.iter.len(), self.window_size), None) } } + +macro_rules! bench_seq_iter_size_hinted { + ($name:ident, $rng:ident, $fn:ident, $length:expr) => { + #[bench] + fn $name(b: &mut Bencher) { + let mut rng = $rng::from_rng(thread_rng()).unwrap(); + let x: &mut [usize] = &mut [1; $length]; + for (i, r) in x.iter_mut().enumerate() { + *r = i; + } + b.iter(|| { + let mut s = 0; + for _ in 0..RAND_BENCH_N { + s += x.iter().$fn(&mut rng).unwrap(); + } + s + }); + b.bytes = size_of::() as u64 * crate::RAND_BENCH_N; + } + }; +} + +macro_rules! bench_seq_iter_unhinted { + ($name:ident,$rng:ident, $fn:ident, $length:expr) => { + #[bench] + fn $name(b: &mut Bencher) { + let mut rng = $rng::from_rng(thread_rng()).unwrap(); + let x: &[usize] = &[1; $length]; + b.iter(|| UnhintedIterator { iter: x.iter() }.$fn(&mut rng).unwrap()) + } + }; +} + +macro_rules! bench_seq_iter_window_hinted { + ($name:ident,$rng:ident, $fn:ident, $length:expr) => { + #[bench] + fn $name(b: &mut Bencher) { + let mut rng = $rng::from_rng(thread_rng()).unwrap(); + let x: &[usize] = &[1; $length]; + b.iter(|| { + WindowHintedIterator { + iter: x.iter(), + window_size: 7, + } + .$fn(&mut rng) + .unwrap() + }) + } + }; +} + +//Size Hinted +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_10000_cryptoRng, + CryptoRng, + choose, + 10000 +); +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_10000_smallRng, + SmallRng, + choose, + 10000 +); +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_1000_cryptoRng, + CryptoRng, + choose, + 1000 +); +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_1000_smallRng, + SmallRng, + choose, + 1000 +); +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_100_cryptoRng, + CryptoRng, + choose, + 100 +); +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_100_smallRng, + SmallRng, + choose, + 100 +); +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_10_smallRng, + SmallRng, + choose, + 10 +); +bench_seq_iter_size_hinted!( + seq_iter_size_hinted_choose_from_10_cryptoRng, + CryptoRng, + choose, + 10 +); + +//Unhinted +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_10000_cryptoRng, + CryptoRng, + choose, + 10000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_10000_smallRng, + SmallRng, + choose, + 10000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_1000_cryptoRng, + CryptoRng, + choose, + 1000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_1000_smallRng, + SmallRng, + choose, + 1000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_100_cryptoRng, + CryptoRng, + choose, + 100 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_100_smallRng, + SmallRng, + choose, + 100 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_10_smallRng, + SmallRng, + choose, + 10 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_10_cryptoRng, + CryptoRng, + choose, + 10 +); + +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_4_smallRng, + SmallRng, + choose, + 4 +); + +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_2_smallRng, + SmallRng, + choose, + 2 +); + +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_1_smallRng, + SmallRng, + choose, + 1 +); + +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_4_cryptoRng, + CryptoRng, + choose, + 4 +); + +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_2_cryptoRng, + CryptoRng, + choose, + 2 +); + +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_from_1_cryptoRng, + CryptoRng, + choose, + 1 +); + +// Window hinted +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_10000_cryptoRng, + CryptoRng, + choose, + 10000 +); +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_10000_smallRng, + SmallRng, + choose, + 10000 +); +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_1000_cryptoRng, + CryptoRng, + choose, + 1000 +); +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_1000_smallRng, + SmallRng, + choose, + 1000 +); + +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_100_cryptoRng, + CryptoRng, + choose, + 100 +); +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_100_smallRng, + SmallRng, + choose, + 100 +); +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_10_smallRng, + SmallRng, + choose, + 10 +); +bench_seq_iter_window_hinted!( + seq_iter_window_hinted_choose_from_10_cryptoRng, + CryptoRng, + choose, + 10 +); + +//Choose Stable +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_10000_cryptoRng, + CryptoRng, + choose_stable, + 10000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_10000_smallRng, + SmallRng, + choose_stable, + 10000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_1000_cryptoRng, + CryptoRng, + choose_stable, + 1000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_1000_smallRng, + SmallRng, + choose_stable, + 1000 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_100_cryptoRng, + CryptoRng, + choose_stable, + 100 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_100_smallRng, + SmallRng, + choose_stable, + 100 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_10_smallRng, + SmallRng, + choose_stable, + 10 +); +bench_seq_iter_unhinted!( + seq_iter_unhinted_choose_stable_from_10_cryptoRng, + CryptoRng, + choose_stable, + 10 +); + +// Window hinted +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_10000_cryptoRng, + CryptoRng, + choose_stable, + 10000 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_10000_smallRng, + SmallRng, + choose_stable, + 10000 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_1000_cryptoRng, + CryptoRng, + choose_stable, + 1000 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_1000_smallRng, + SmallRng, + choose_stable, + 1000 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_100_cryptoRng, + CryptoRng, + choose_stable, + 100 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_100_smallRng, + SmallRng, + choose_stable, + 100 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_10_smallRng, + SmallRng, + choose_stable, + 10 +); +bench_seq_iter_unhinted!( + seq_iter_stable_choose_from_10_cryptoRng, + CryptoRng, + choose_stable, + 10 +); + From b3062e5557583d2aa882d2410e2e84c8082cc704 Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Mon, 5 Dec 2022 19:43:09 +0000 Subject: [PATCH 15/26] Added seq_choose benches for smaller numbers --- benches/seq_choose.rs | 362 ++++++++---------------------------------- 1 file changed, 65 insertions(+), 297 deletions(-) diff --git a/benches/seq_choose.rs b/benches/seq_choose.rs index 8704702a41..86d86ec77b 100644 --- a/benches/seq_choose.rs +++ b/benches/seq_choose.rs @@ -8,13 +8,16 @@ #![feature(test)] #![allow(non_snake_case)] +#![feature(custom_inner_attributes)] +// Rustfmt splits macro invocations to shorten lines; in this case longer-lines are more readable +#![rustfmt::skip] extern crate test; use test::Bencher; -use rand::prelude::*; use core::mem::size_of; +use rand::prelude::*; // We force use of 32-bit RNG since seq code is optimised for use with 32-bit // generators on all platforms. @@ -23,11 +26,9 @@ use rand_pcg::Pcg32 as SmallRng; const RAND_BENCH_N: u64 = 1000; - #[derive(Clone)] struct UnhintedIterator { - iter: I, -} + iter: I, } impl Iterator for UnhintedIterator { type Item = I::Item; @@ -38,9 +39,7 @@ impl Iterator for UnhintedIterator { #[derive(Clone)] struct WindowHintedIterator { - iter: I, - window_size: usize, -} + iter: I, window_size: usize, } impl Iterator for WindowHintedIterator { type Item = I::Item; @@ -75,7 +74,7 @@ macro_rules! bench_seq_iter_size_hinted { } macro_rules! bench_seq_iter_unhinted { - ($name:ident,$rng:ident, $fn:ident, $length:expr) => { + ($name:ident,$rng:ident, $fn:ident, $length:expr) => { #[bench] fn $name(b: &mut Bencher) { let mut rng = $rng::from_rng(thread_rng()).unwrap(); @@ -86,16 +85,14 @@ macro_rules! bench_seq_iter_unhinted { } macro_rules! bench_seq_iter_window_hinted { - ($name:ident,$rng:ident, $fn:ident, $length:expr) => { + ($name:ident,$rng:ident, $fn:ident, $length:expr) => { #[bench] fn $name(b: &mut Bencher) { let mut rng = $rng::from_rng(thread_rng()).unwrap(); let x: &[usize] = &[1; $length]; b.iter(|| { WindowHintedIterator { - iter: x.iter(), - window_size: 7, - } + iter: x.iter(), window_size: 7, } .$fn(&mut rng) .unwrap() }) @@ -104,295 +101,66 @@ macro_rules! bench_seq_iter_window_hinted { } //Size Hinted -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_10000_cryptoRng, - CryptoRng, - choose, - 10000 -); -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_10000_smallRng, - SmallRng, - choose, - 10000 -); -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_1000_cryptoRng, - CryptoRng, - choose, - 1000 -); -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_1000_smallRng, - SmallRng, - choose, - 1000 -); -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_100_cryptoRng, - CryptoRng, - choose, - 100 -); -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_100_smallRng, - SmallRng, - choose, - 100 -); -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_10_smallRng, - SmallRng, - choose, - 10 -); -bench_seq_iter_size_hinted!( - seq_iter_size_hinted_choose_from_10_cryptoRng, - CryptoRng, - choose, - 10 -); +bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_10000_cryptoRng, CryptoRng, choose, 10000); +bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_10000_smallRng, SmallRng, choose, 10000); +bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_1000_cryptoRng, CryptoRng, choose, 1000); +bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_1000_smallRng, SmallRng, choose, 1000); +bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_100_cryptoRng, CryptoRng, choose, 100); +bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_100_smallRng, SmallRng, choose, 100); +bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_10_smallRng, SmallRng, choose, 10); +bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_10_cryptoRng, CryptoRng, choose, 10); +bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_3_smallRng, SmallRng, choose, 3); +bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_3_cryptoRng, CryptoRng, choose, 3); +bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_2_smallRng, SmallRng, choose, 2); +bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_2_cryptoRng, CryptoRng, choose, 2); +bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_1_smallRng, SmallRng, choose, 1); +bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_1_cryptoRng, CryptoRng, choose, 1); //Unhinted -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_10000_cryptoRng, - CryptoRng, - choose, - 10000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_10000_smallRng, - SmallRng, - choose, - 10000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_1000_cryptoRng, - CryptoRng, - choose, - 1000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_1000_smallRng, - SmallRng, - choose, - 1000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_100_cryptoRng, - CryptoRng, - choose, - 100 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_100_smallRng, - SmallRng, - choose, - 100 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_10_smallRng, - SmallRng, - choose, - 10 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_10_cryptoRng, - CryptoRng, - choose, - 10 -); - -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_4_smallRng, - SmallRng, - choose, - 4 -); - -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_2_smallRng, - SmallRng, - choose, - 2 -); - -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_1_smallRng, - SmallRng, - choose, - 1 -); - -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_4_cryptoRng, - CryptoRng, - choose, - 4 -); - -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_2_cryptoRng, - CryptoRng, - choose, - 2 -); - -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_from_1_cryptoRng, - CryptoRng, - choose, - 1 -); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_10000_cryptoRng, CryptoRng, choose, 10000); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_10000_smallRng, SmallRng, choose, 10000); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_1000_cryptoRng, CryptoRng, choose, 1000); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_1000_smallRng, SmallRng, choose, 1000); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_100_cryptoRng, CryptoRng, choose, 100); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_100_smallRng, SmallRng, choose, 100); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_10_smallRng, SmallRng, choose, 10); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_10_cryptoRng, CryptoRng, choose, 10); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_3_smallRng, SmallRng, choose, 3); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_3_cryptoRng, CryptoRng, choose, 3); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_2_smallRng, SmallRng, choose, 2); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_2_cryptoRng, CryptoRng, choose, 2); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_1_smallRng, SmallRng, choose, 1); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_1_cryptoRng, CryptoRng, choose, 1); // Window hinted -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_10000_cryptoRng, - CryptoRng, - choose, - 10000 -); -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_10000_smallRng, - SmallRng, - choose, - 10000 -); -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_1000_cryptoRng, - CryptoRng, - choose, - 1000 -); -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_1000_smallRng, - SmallRng, - choose, - 1000 -); - -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_100_cryptoRng, - CryptoRng, - choose, - 100 -); -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_100_smallRng, - SmallRng, - choose, - 100 -); -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_10_smallRng, - SmallRng, - choose, - 10 -); -bench_seq_iter_window_hinted!( - seq_iter_window_hinted_choose_from_10_cryptoRng, - CryptoRng, - choose, - 10 -); +bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_10000_cryptoRng, CryptoRng, choose, 10000); +bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_10000_smallRng, SmallRng, choose, 10000); +bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_1000_cryptoRng, CryptoRng, choose, 1000); +bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_1000_smallRng, SmallRng, choose, 1000); +bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_100_cryptoRng, CryptoRng, choose, 100); +bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_100_smallRng, SmallRng, choose, 100); +bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_10_smallRng, SmallRng, choose, 10); +bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_10_cryptoRng, CryptoRng, choose, 10); +bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_3_smallRng, SmallRng, choose, 3); +bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_3_cryptoRng, CryptoRng, choose, 3); +bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_2_smallRng, SmallRng, choose, 2); +bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_2_cryptoRng, CryptoRng, choose, 2); +bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_1_smallRng, SmallRng, choose, 1); +bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_1_cryptoRng, CryptoRng, choose, 1); //Choose Stable -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_10000_cryptoRng, - CryptoRng, - choose_stable, - 10000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_10000_smallRng, - SmallRng, - choose_stable, - 10000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_1000_cryptoRng, - CryptoRng, - choose_stable, - 1000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_1000_smallRng, - SmallRng, - choose_stable, - 1000 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_100_cryptoRng, - CryptoRng, - choose_stable, - 100 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_100_smallRng, - SmallRng, - choose_stable, - 100 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_10_smallRng, - SmallRng, - choose_stable, - 10 -); -bench_seq_iter_unhinted!( - seq_iter_unhinted_choose_stable_from_10_cryptoRng, - CryptoRng, - choose_stable, - 10 -); - -// Window hinted -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_10000_cryptoRng, - CryptoRng, - choose_stable, - 10000 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_10000_smallRng, - SmallRng, - choose_stable, - 10000 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_1000_cryptoRng, - CryptoRng, - choose_stable, - 1000 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_1000_smallRng, - SmallRng, - choose_stable, - 1000 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_100_cryptoRng, - CryptoRng, - choose_stable, - 100 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_100_smallRng, - SmallRng, - choose_stable, - 100 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_10_smallRng, - SmallRng, - choose_stable, - 10 -); -bench_seq_iter_unhinted!( - seq_iter_stable_choose_from_10_cryptoRng, - CryptoRng, - choose_stable, - 10 -); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_10000_smallRng, SmallRng, choose_stable, 10000); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_10000_cryptoRng, CryptoRng, choose_stable, 10000); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_1000_smallRng, SmallRng, choose_stable, 1000); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_1000_cryptoRng, CryptoRng, choose_stable, 1000); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_100_smallRng, SmallRng, choose_stable, 100); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_100_cryptoRng, CryptoRng, choose_stable, 100); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_10_smallRng, SmallRng, choose_stable, 10); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_10_cryptoRng, CryptoRng, choose_stable, 10); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_3_smallRng, SmallRng, choose_stable, 3); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_3_cryptoRng, CryptoRng, choose_stable, 3); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_2_smallRng, SmallRng, choose_stable, 2); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_2_cryptoRng, CryptoRng, choose_stable, 2); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_1_smallRng, SmallRng, choose_stable, 1); +bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_1_cryptoRng, CryptoRng, choose_stable, 1); From a7a7a9004afec39fe82b3260bd3a41824fe19172 Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Mon, 5 Dec 2022 20:36:41 +0000 Subject: [PATCH 16/26] Removed some unneeded lines from seq_choose --- benches/seq_choose.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/benches/seq_choose.rs b/benches/seq_choose.rs index 86d86ec77b..3c755ec285 100644 --- a/benches/seq_choose.rs +++ b/benches/seq_choose.rs @@ -15,8 +15,6 @@ extern crate test; use test::Bencher; - -use core::mem::size_of; use rand::prelude::*; // We force use of 32-bit RNG since seq code is optimised for use with 32-bit @@ -68,7 +66,6 @@ macro_rules! bench_seq_iter_size_hinted { } s }); - b.bytes = size_of::() as u64 * crate::RAND_BENCH_N; } }; } From 4726a604f1f5e0e7be65c0a93606cff90c8cfb6b Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Fri, 9 Dec 2022 17:17:59 +0000 Subject: [PATCH 17/26] Improvements in coin_flipper.rs --- src/seq/coin_flipper.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/seq/coin_flipper.rs b/src/seq/coin_flipper.rs index 1eaf003a2c..ce9fc6438a 100644 --- a/src/seq/coin_flipper.rs +++ b/src/seq/coin_flipper.rs @@ -18,13 +18,15 @@ impl CoinFlipper { #[inline] /// Returns true with a probability of 1 / d /// Uses an expected two bits of randomness + /// Panics if d == 0 pub fn gen_ratio_one_over(&mut self, d: usize) -> bool { + debug_assert_ne!(d, 0); // This uses the same logic as `gen_ratio` but is optimized for the case that the starting numerator is one (which it always is for `Sequence::Choose()`) // In this case (unlike in `gen_ratio`), this way of calculating c is always accurate let c = (usize::BITS - 1 - d.leading_zeros()).min(32); - if self.flip_until_tails(c) { + if self.flip_c_heads(c) { let numerator = 1 << c; return self.gen_ratio(numerator, d); } else { @@ -63,11 +65,11 @@ impl CoinFlipper { while n < d { //Find a good value for c by counting leading zeros //This will either give the highest possible c, or 1 less than that - let c = n.leading_zeros().saturating_sub(d.leading_zeros() + 1).min(32).max(1); + let c = n.leading_zeros().saturating_sub(d.leading_zeros() + 1).clamp(1, 32); // set next_n to n * 2^c (checked_shl will fail if 2n >= `usize::max`) if let Some(next_n) = n.checked_shl(c) { - if self.flip_until_tails(c) { + if self.flip_c_heads(c) { //All heads //if 2n < d, set n to 2n //if 2n >= d, the while loop will exit and we will return `true` @@ -84,26 +86,26 @@ impl CoinFlipper { } else { // This branch will only be reached when 2n >= `usize::max` // Obviously 2n > d - if self.flip_until_tails(1) { + if self.flip_c_heads(1) { //heads return true; } else { //tails - n = n.saturating_add(n).saturating_sub(d); // set n to 2n -d + n = n.wrapping_add(n).saturating_sub(d); // set n to 2n -d } } } true } - /// If the next `c` bits of randomness are all zeroes, consume them and return true. - /// Otherwise return false and consume the number of zeroes plus one - /// Generates new bits of randomness when necessary (int 32 bit chunks) - /// Has a one in 2 to the `c` chance of returning true + /// If the next `c` bits of randomness all represent heads, consume them and return true. + /// Otherwise return false and consume the number of heads plus one. + /// Generates new bits of randomness when necessary (in 32 bit chunks) + /// Has a 1 in 2 to the `c` chance of returning true /// `c` must be less than or equal to 32 - fn flip_until_tails(&mut self, mut c: u32) -> bool { - debug_assert!(c <= 32); //If `c` > 32 this wil always return false - //Note that zeros on the left of the chunk represent heads. It needs to be this way round because zeros are filled in when left shifting + fn flip_c_heads(&mut self, mut c: u32) -> bool { + debug_assert!(c <= 32); + //Note that zeros on the left of the chunk represent heads. It needs to be this way round because zeros are filled in when left shifting loop { let zeros = self.chunk.leading_zeros(); From 68dc604a5443c786e1b5ec69436c692bed577709 Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Fri, 9 Dec 2022 21:37:32 +0000 Subject: [PATCH 18/26] Small refactor of coin_flipper --- src/seq/coin_flipper.rs | 57 +++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/src/seq/coin_flipper.rs b/src/seq/coin_flipper.rs index ce9fc6438a..8dd9d805cb 100644 --- a/src/seq/coin_flipper.rs +++ b/src/seq/coin_flipper.rs @@ -38,7 +38,7 @@ impl CoinFlipper { /// Returns true with a probability of n / d /// Uses an expected two bits of randomness fn gen_ratio(&mut self, mut n: usize, d: usize) -> bool { - // Explanation: + // Explanation: // We are trying to return true with a probability of n / d // If n >= d, we can just return true // Otherwise there are two possibilities 2n < d and 2n >= d @@ -65,46 +65,43 @@ impl CoinFlipper { while n < d { //Find a good value for c by counting leading zeros //This will either give the highest possible c, or 1 less than that - let c = n.leading_zeros().saturating_sub(d.leading_zeros() + 1).clamp(1, 32); + let c = n + .leading_zeros() + .saturating_sub(d.leading_zeros() + 1) + .clamp(1, 32); - // set next_n to n * 2^c (checked_shl will fail if 2n >= `usize::max`) - if let Some(next_n) = n.checked_shl(c) { - if self.flip_c_heads(c) { - //All heads - //if 2n < d, set n to 2n - //if 2n >= d, the while loop will exit and we will return `true` - n = next_n - } else { - //At least one tail - either return false or set n to 2n-d - n = next_n.saturating_sub(d); - - if n == 0 { - //Because we used saturating_sub, n will be zero if 2n was less than d or 2n was equal to d, in either case we can return false. + if self.flip_c_heads(c) { + // All heads + // Set n to n * 2^c + // If 2n >= d, the while loop will exit and we will return `true` + // If n * 2^c > `usize::MAX` then using `saturating_mul` will give `usize::MAX` and the loop will exit returning true + n = n.saturating_mul(2_usize.pow(c)); + } else { + //At least one tail + if c == 1 { + //Calculate 2n - d. We need to use wrapping as 2n might be greater than `usize::MAX` + let next_n = n.wrapping_add(n).wrapping_sub(d); + if next_n == 0 || next_n > n { + //This will happen if 2n < d return false; } - } - } else { - // This branch will only be reached when 2n >= `usize::max` - // Obviously 2n > d - if self.flip_c_heads(1) { - //heads - return true; + n = next_n; } else { - //tails - n = n.wrapping_add(n).saturating_sub(d); // set n to 2n -d + //c > 1 so 2n < d so we can return false + return false; } } } true } - + /// If the next `c` bits of randomness all represent heads, consume them and return true. /// Otherwise return false and consume the number of heads plus one. /// Generates new bits of randomness when necessary (in 32 bit chunks) /// Has a 1 in 2 to the `c` chance of returning true /// `c` must be less than or equal to 32 - fn flip_c_heads(&mut self, mut c: u32) -> bool { - debug_assert!(c <= 32); + fn flip_c_heads(&mut self, mut c: u32) -> bool { + debug_assert!(c <= 32); //Note that zeros on the left of the chunk represent heads. It needs to be this way round because zeros are filled in when left shifting loop { let zeros = self.chunk.leading_zeros(); @@ -113,8 +110,8 @@ impl CoinFlipper { // The happy path - we found a 1 and can return false // Note that because a 1 bit was detected, we cannot have run out of random bits so we don't need to check - // First consume all of the bits read - self.chunk = self.chunk.wrapping_shl(zeros + 1); + // First consume all of the bits read + self.chunk = self.chunk.wrapping_shl(zeros + 1); //using regular shl seems to give worse performance for size-hinted iterators self.chunk_remaining = self.chunk_remaining.saturating_sub(zeros + 1); return false; } else { @@ -137,4 +134,4 @@ impl CoinFlipper { } } } -} \ No newline at end of file +} From 8723cda520d35d9746feb4d80d7867aa6ac3eb85 Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Fri, 9 Dec 2022 21:53:31 +0000 Subject: [PATCH 19/26] Tidied comments in coin_flipper --- src/seq/coin_flipper.rs | 67 +++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/src/seq/coin_flipper.rs b/src/seq/coin_flipper.rs index 8dd9d805cb..e754508979 100644 --- a/src/seq/coin_flipper.rs +++ b/src/seq/coin_flipper.rs @@ -21,9 +21,10 @@ impl CoinFlipper { /// Panics if d == 0 pub fn gen_ratio_one_over(&mut self, d: usize) -> bool { debug_assert_ne!(d, 0); - // This uses the same logic as `gen_ratio` but is optimized for the case that the starting numerator is one (which it always is for `Sequence::Choose()`) + // This uses the same logic as `gen_ratio` but is optimized for the case that + // the starting numerator is one (which it always is for `Sequence::Choose()`) - // In this case (unlike in `gen_ratio`), this way of calculating c is always accurate + // In this case (but not `gen_ratio`), this way of calculating c is always accurate let c = (usize::BITS - 1 - d.leading_zeros()).min(32); if self.flip_c_heads(c) { @@ -46,25 +47,31 @@ impl CoinFlipper { // If 2n < d // If it comes up tails, return false // If it comes up heads, double n and start again - // This is fair because (0.5 * 0) + (0.5 * 2n / d) = n / d and 2n is less than d (if 2n was greater than d we would effectively round it down to 1 by returning true) + // This is fair because (0.5 * 0) + (0.5 * 2n / d) = n / d and 2n is less than d + // (if 2n was greater than d we would effectively round it down to 1 + // by returning true) // If 2n >= d - // If it comes up tails, set n to 2n - d and start again - // If it comes up heads, return true - // This is fair because (0.5 * 1) + (0.5 * (2n - d) / d) = n / d - // Note that if 2n = d and the coin comes up tails, n will be set to 0 before restarting which is equivalent to returning false. + // If it comes up tails, set n to 2n - d and start again + // If it comes up heads, return true + // This is fair because (0.5 * 1) + (0.5 * (2n - d) / d) = n / d + // Note that if 2n = d and the coin comes up tails, n will be set to 0 + // before restarting which is equivalent to returning false. + + // As a performance optimization we can flip multiple coins at once + // This is efficient because we can use the `lzcnt` intrinsic + // We can check up to 32 flips at once but we only receive one bit of information + // - all heads or at least one tail. - // As a performance optimization we can flip multiple coins at once (using the `lzcnt` intrinsic) - // We can check up to 32 flips at once but we only receive one bit of information - all heads or at least one tail. // Let c be the number of coins to flip. 1 <= c <= 32 // If 2n < d, n * 2^c < d // If the result is all heads, then set n to n * 2^c // If there was at least one tail, return false - // If 2n >= d, the order of the heads and tails matters so we flip one coin at a time so c = 1 + // If 2n >= d, the order of results matters so we flip one coin at a time so c = 1 // Ideally, c will be as high as possible within these constraints while n < d { - //Find a good value for c by counting leading zeros - //This will either give the highest possible c, or 1 less than that + // Find a good value for c by counting leading zeros + // This will either give the highest possible c, or 1 less than that let c = n .leading_zeros() .saturating_sub(d.leading_zeros() + 1) @@ -74,20 +81,21 @@ impl CoinFlipper { // All heads // Set n to n * 2^c // If 2n >= d, the while loop will exit and we will return `true` - // If n * 2^c > `usize::MAX` then using `saturating_mul` will give `usize::MAX` and the loop will exit returning true + // If n * 2^c > `usize::MAX` we always return `true` anyway n = n.saturating_mul(2_usize.pow(c)); } else { //At least one tail if c == 1 { - //Calculate 2n - d. We need to use wrapping as 2n might be greater than `usize::MAX` + // Calculate 2n - d. + // We need to use wrapping as 2n might be greater than `usize::MAX` let next_n = n.wrapping_add(n).wrapping_sub(d); if next_n == 0 || next_n > n { - //This will happen if 2n < d + // This will happen if 2n < d return false; } n = next_n; } else { - //c > 1 so 2n < d so we can return false + // c > 1 so 2n < d so we can return false return false; } } @@ -95,41 +103,48 @@ impl CoinFlipper { true } - /// If the next `c` bits of randomness all represent heads, consume them and return true. + /// If the next `c` bits of randomness all represent heads, consume them, return true /// Otherwise return false and consume the number of heads plus one. /// Generates new bits of randomness when necessary (in 32 bit chunks) /// Has a 1 in 2 to the `c` chance of returning true /// `c` must be less than or equal to 32 fn flip_c_heads(&mut self, mut c: u32) -> bool { debug_assert!(c <= 32); - //Note that zeros on the left of the chunk represent heads. It needs to be this way round because zeros are filled in when left shifting + // Note that zeros on the left of the chunk represent heads. + // It needs to be this way round because zeros are filled in when left shifting loop { let zeros = self.chunk.leading_zeros(); if zeros < c { // The happy path - we found a 1 and can return false - // Note that because a 1 bit was detected, we cannot have run out of random bits so we don't need to check + // Note that because a 1 bit was detected, + // We cannot have run out of random bits so we don't need to check + + // First consume all of the bits read + // Using shl seems to give worse performance for size-hinted iterators + self.chunk = self.chunk.wrapping_shl(zeros + 1); - // First consume all of the bits read - self.chunk = self.chunk.wrapping_shl(zeros + 1); //using regular shl seems to give worse performance for size-hinted iterators self.chunk_remaining = self.chunk_remaining.saturating_sub(zeros + 1); return false; } else { // The number of zeros is larger than `c` - //There are two possibilities + // There are two possibilities if let Some(new_remaining) = self.chunk_remaining.checked_sub(c) { - //Those zeroes were all part of our random chunk, so throw away `c` bits of randomness and return true + // Those zeroes were all part of our random chunk, + // throw away `c` bits of randomness and return true self.chunk_remaining = new_remaining; self.chunk <<= c; return true; } else { - // Some of those zeroes were part of the random chunk and some were part of the space behind it - c -= self.chunk_remaining; //Take into account the zeroes that were random + // Some of those zeroes were part of the random chunk + // and some were part of the space behind it + // We need to take into account only the zeroes that were random + c -= self.chunk_remaining; // Generate a new chunk self.chunk = self.rng.next_u32(); self.chunk_remaining = 32; - //Go back to start of loop + // Go back to start of loop } } } From a2c4cce25af2f860ca9a90749dc9770c238933fc Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Fri, 9 Dec 2022 22:05:17 +0000 Subject: [PATCH 20/26] Use criterion for seq_choose benchmarks --- Cargo.toml | 7 ++ benches/seq_choose.rs | 216 ++++++++++++++++++------------------------ 2 files changed, 98 insertions(+), 125 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bc39334db6..6d58ef8d8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,3 +73,10 @@ rand_pcg = { path = "rand_pcg", version = "0.3.0" } # Only to test serde1 bincode = "1.2.1" rayon = "1.5.3" +criterion = { version = "0.4" } + + +[[bench]] +name = "seq_choose" +path = "benches/seq_choose.rs" +harness = false \ No newline at end of file diff --git a/benches/seq_choose.rs b/benches/seq_choose.rs index 3c755ec285..7682f25e0e 100644 --- a/benches/seq_choose.rs +++ b/benches/seq_choose.rs @@ -1,28 +1,106 @@ -// Copyright 2018 Developers of the Rand project. +// Copyright 2018-2022 Developers of the Rand project. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. - -#![feature(test)] -#![allow(non_snake_case)] -#![feature(custom_inner_attributes)] -// Rustfmt splits macro invocations to shorten lines; in this case longer-lines are more readable -#![rustfmt::skip] - -extern crate test; - -use test::Bencher; +use criterion::{criterion_group, criterion_main, Criterion, black_box}; use rand::prelude::*; +use rand_chacha::ChaCha20Rng as CryptoRng; // We force use of 32-bit RNG since seq code is optimised for use with 32-bit // generators on all platforms. -use rand_chacha::ChaCha20Rng as CryptoRng; use rand_pcg::Pcg32 as SmallRng; -const RAND_BENCH_N: u64 = 1000; +criterion_group!( +name = benches; +config = Criterion::default(); +targets = bench +); +criterion_main!(benches); + +pub fn bench(c: &mut Criterion) { + for length in [1, 2, 3, 10, 100, 1000].map(|x| black_box(x)) { + c.bench_function(format!("choose_size-hinted_from_{length}_small").as_str(), |b| { + let mut rng = SmallRng::seed_from_u64(123); + b.iter(|| choose_size_hinted(length, &mut rng)) + }); + + c.bench_function(format!("choose_stable_from_{length}_small").as_str(), |b| { + let mut rng = SmallRng::seed_from_u64(123); + b.iter(|| choose_stable(length, &mut rng)) + }); + + c.bench_function( + format!("choose_unhinted_from_{length}_small").as_str(), + |b| { + let mut rng = SmallRng::seed_from_u64(123); + b.iter(|| choose_unhinted(length, &mut rng)) + }, + ); + + c.bench_function( + format!("choose_windowed_from_{length}_small").as_str(), + |b| { + let mut rng = SmallRng::seed_from_u64(123); + b.iter(|| choose_windowed(length, 7, &mut rng)) + }, + ); + + c.bench_function(format!("choose_size-hinted-from_{length}_crypto").as_str(), |b| { + let mut rng = CryptoRng::seed_from_u64(123); + b.iter(|| choose_size_hinted(length, &mut rng)) + }); + + c.bench_function( + format!("choose_stable_from_{length}_crypto").as_str(), + |b| { + let mut rng = CryptoRng::seed_from_u64(123); + b.iter(|| choose_stable(length, &mut rng)) + }, + ); + + c.bench_function( + format!("choose_unhinted_from_{length}_crypto").as_str(), + |b| { + let mut rng = CryptoRng::seed_from_u64(123); + b.iter(|| choose_unhinted(length, &mut rng)) + }, + ); + + c.bench_function( + format!("choose_windowed_from_{length}_crypto").as_str(), + |b| { + let mut rng = CryptoRng::seed_from_u64(123); + b.iter(|| choose_windowed(length, 7, &mut rng)) + }, + ); + } +} + +fn choose_size_hinted(max: usize, rng: &mut R) -> Option { + let iterator = 0..max; + iterator.choose(rng) +} + +fn choose_stable(max: usize, rng: &mut R) -> Option { + let iterator = 0..max; + iterator.choose_stable(rng) +} + +fn choose_unhinted(max: usize, rng: &mut R) -> Option { + let iterator = UnhintedIterator { iter: (0..max) }; + iterator.choose(rng) +} + +fn choose_windowed(max: usize, window_size: usize, rng: &mut R) -> Option { + let iterator = WindowHintedIterator { + iter: (0..max), + window_size, + }; + iterator.choose(rng) +} #[derive(Clone)] struct UnhintedIterator { @@ -49,115 +127,3 @@ impl Iterator for WindowHintedIterator< (core::cmp::min(self.iter.len(), self.window_size), None) } } - -macro_rules! bench_seq_iter_size_hinted { - ($name:ident, $rng:ident, $fn:ident, $length:expr) => { - #[bench] - fn $name(b: &mut Bencher) { - let mut rng = $rng::from_rng(thread_rng()).unwrap(); - let x: &mut [usize] = &mut [1; $length]; - for (i, r) in x.iter_mut().enumerate() { - *r = i; - } - b.iter(|| { - let mut s = 0; - for _ in 0..RAND_BENCH_N { - s += x.iter().$fn(&mut rng).unwrap(); - } - s - }); - } - }; -} - -macro_rules! bench_seq_iter_unhinted { - ($name:ident,$rng:ident, $fn:ident, $length:expr) => { - #[bench] - fn $name(b: &mut Bencher) { - let mut rng = $rng::from_rng(thread_rng()).unwrap(); - let x: &[usize] = &[1; $length]; - b.iter(|| UnhintedIterator { iter: x.iter() }.$fn(&mut rng).unwrap()) - } - }; -} - -macro_rules! bench_seq_iter_window_hinted { - ($name:ident,$rng:ident, $fn:ident, $length:expr) => { - #[bench] - fn $name(b: &mut Bencher) { - let mut rng = $rng::from_rng(thread_rng()).unwrap(); - let x: &[usize] = &[1; $length]; - b.iter(|| { - WindowHintedIterator { - iter: x.iter(), window_size: 7, } - .$fn(&mut rng) - .unwrap() - }) - } - }; -} - -//Size Hinted -bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_10000_cryptoRng, CryptoRng, choose, 10000); -bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_10000_smallRng, SmallRng, choose, 10000); -bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_1000_cryptoRng, CryptoRng, choose, 1000); -bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_1000_smallRng, SmallRng, choose, 1000); -bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_100_cryptoRng, CryptoRng, choose, 100); -bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_100_smallRng, SmallRng, choose, 100); -bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_10_smallRng, SmallRng, choose, 10); -bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_10_cryptoRng, CryptoRng, choose, 10); -bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_3_smallRng, SmallRng, choose, 3); -bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_3_cryptoRng, CryptoRng, choose, 3); -bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_2_smallRng, SmallRng, choose, 2); -bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_2_cryptoRng, CryptoRng, choose, 2); -bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_1_smallRng, SmallRng, choose, 1); -bench_seq_iter_size_hinted!(seq_iter_size_hinted_choose_from_1_cryptoRng, CryptoRng, choose, 1); - -//Unhinted -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_10000_cryptoRng, CryptoRng, choose, 10000); -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_10000_smallRng, SmallRng, choose, 10000); -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_1000_cryptoRng, CryptoRng, choose, 1000); -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_1000_smallRng, SmallRng, choose, 1000); -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_100_cryptoRng, CryptoRng, choose, 100); -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_100_smallRng, SmallRng, choose, 100); -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_10_smallRng, SmallRng, choose, 10); -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_10_cryptoRng, CryptoRng, choose, 10); -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_3_smallRng, SmallRng, choose, 3); -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_3_cryptoRng, CryptoRng, choose, 3); -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_2_smallRng, SmallRng, choose, 2); -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_2_cryptoRng, CryptoRng, choose, 2); -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_1_smallRng, SmallRng, choose, 1); -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_from_1_cryptoRng, CryptoRng, choose, 1); - -// Window hinted -bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_10000_cryptoRng, CryptoRng, choose, 10000); -bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_10000_smallRng, SmallRng, choose, 10000); -bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_1000_cryptoRng, CryptoRng, choose, 1000); -bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_1000_smallRng, SmallRng, choose, 1000); -bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_100_cryptoRng, CryptoRng, choose, 100); -bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_100_smallRng, SmallRng, choose, 100); -bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_10_smallRng, SmallRng, choose, 10); -bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_10_cryptoRng, CryptoRng, choose, 10); -bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_3_smallRng, SmallRng, choose, 3); -bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_3_cryptoRng, CryptoRng, choose, 3); -bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_2_smallRng, SmallRng, choose, 2); -bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_2_cryptoRng, CryptoRng, choose, 2); -bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_1_smallRng, SmallRng, choose, 1); -bench_seq_iter_window_hinted!(seq_iter_window_hinted_choose_from_1_cryptoRng, CryptoRng, choose, 1); - -//Choose Stable -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_10000_smallRng, SmallRng, choose_stable, 10000); -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_10000_cryptoRng, CryptoRng, choose_stable, 10000); -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_1000_smallRng, SmallRng, choose_stable, 1000); -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_1000_cryptoRng, CryptoRng, choose_stable, 1000); -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_100_smallRng, SmallRng, choose_stable, 100); -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_100_cryptoRng, CryptoRng, choose_stable, 100); -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_10_smallRng, SmallRng, choose_stable, 10); -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_10_cryptoRng, CryptoRng, choose_stable, 10); -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_3_smallRng, SmallRng, choose_stable, 3); -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_3_cryptoRng, CryptoRng, choose_stable, 3); -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_2_smallRng, SmallRng, choose_stable, 2); -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_2_cryptoRng, CryptoRng, choose_stable, 2); -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_1_smallRng, SmallRng, choose_stable, 1); -bench_seq_iter_unhinted!(seq_iter_unhinted_choose_stable_from_1_cryptoRng, CryptoRng, choose_stable, 1); - From 8601cd6e80f7c0f172050b676d513813bc5b4d88 Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Fri, 9 Dec 2022 22:59:37 +0000 Subject: [PATCH 21/26] Made choose not generate a random number if len=1 --- src/seq/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/seq/mod.rs b/src/seq/mod.rs index c6b338432b..dfc3fefeb3 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -331,8 +331,10 @@ pub trait IteratorRandom: Iterator + Sized { if upper == Some(lower) { return if lower == 0 { None - } else { - self.nth(gen_index(rng, lower)) + } + else { + let index = if lower == 1 {0} else {gen_index(rng, lower)}; + self.nth(index) }; } @@ -692,6 +694,7 @@ impl<'a, S: Index + ?Sized + 'a, T: 'a> ExactSizeIterator // platforms. #[inline] fn gen_index(rng: &mut R, ubound: usize) -> usize { + if ubound <= (core::u32::MAX as usize) { rng.gen_range(0..ubound as u32) as usize } else { From f6e7fec3f28015ceb99b3b0c3f6bbf58c55e8142 Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Wed, 4 Jan 2023 15:41:01 +0000 Subject: [PATCH 22/26] small change to IteratorRandom::choose --- src/seq/mod.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/seq/mod.rs b/src/seq/mod.rs index dfc3fefeb3..15fca6fc9c 100644 --- a/src/seq/mod.rs +++ b/src/seq/mod.rs @@ -329,12 +329,10 @@ pub trait IteratorRandom: Iterator + Sized { // when the Iterator is an ExactSizeIterator. This has a large performance impact on e.g. // seq_iter_choose_from_1000. if upper == Some(lower) { - return if lower == 0 { - None - } - else { - let index = if lower == 1 {0} else {gen_index(rng, lower)}; - self.nth(index) + return match lower { + 0 => None, + 1 => self.next(), + _ => self.nth(gen_index(rng, lower)), }; } From 999104c650a32231f9ab2f0afff46434e33cd4bc Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Wed, 4 Jan 2023 17:02:21 +0000 Subject: [PATCH 23/26] Made it easier to change seq_choose benchmarks RNG --- benches/seq_choose.rs | 65 ++++++++++++++++--------------------------- 1 file changed, 24 insertions(+), 41 deletions(-) diff --git a/benches/seq_choose.rs b/benches/seq_choose.rs index 7682f25e0e..4de6cb6992 100644 --- a/benches/seq_choose.rs +++ b/benches/seq_choose.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + // Copyright 2018-2022 Developers of the Rand project. // // Licensed under the Apache License, Version 2.0 , at your // option. This file may not be copied, modified, or distributed // except according to those terms. -use criterion::{criterion_group, criterion_main, Criterion, black_box}; +use criterion::{black_box, criterion_group, criterion_main, Criterion}; use rand::prelude::*; - -use rand_chacha::ChaCha20Rng as CryptoRng; -// We force use of 32-bit RNG since seq code is optimised for use with 32-bit -// generators on all platforms. -use rand_pcg::Pcg32 as SmallRng; +use rand::SeedableRng; criterion_group!( name = benches; -config = Criterion::default(); +config = Criterion::default().warm_up_time(Duration::from_millis(500)).measurement_time(Duration::from_millis(1000)); targets = bench ); criterion_main!(benches); pub fn bench(c: &mut Criterion) { - for length in [1, 2, 3, 10, 100, 1000].map(|x| black_box(x)) { - c.bench_function(format!("choose_size-hinted_from_{length}_small").as_str(), |b| { - let mut rng = SmallRng::seed_from_u64(123); - b.iter(|| choose_size_hinted(length, &mut rng)) - }); - - c.bench_function(format!("choose_stable_from_{length}_small").as_str(), |b| { - let mut rng = SmallRng::seed_from_u64(123); - b.iter(|| choose_stable(length, &mut rng)) - }); - - c.bench_function( - format!("choose_unhinted_from_{length}_small").as_str(), - |b| { - let mut rng = SmallRng::seed_from_u64(123); - b.iter(|| choose_unhinted(length, &mut rng)) - }, - ); + bench_rng::(c, "ChaCha20"); + bench_rng::(c, "Pcg32"); +} +fn bench_rng(c: &mut Criterion, rng_name: &'static str) { + for length in [1, 2, 3, 10, 100, 1000].map(|x| black_box(x)) { c.bench_function( - format!("choose_windowed_from_{length}_small").as_str(), + format!("choose_size-hinted_from_{length}_{rng_name}").as_str(), |b| { - let mut rng = SmallRng::seed_from_u64(123); - b.iter(|| choose_windowed(length, 7, &mut rng)) + let mut rng = Rng::seed_from_u64(123); + b.iter(|| choose_size_hinted(length, &mut rng)) }, ); - c.bench_function(format!("choose_size-hinted-from_{length}_crypto").as_str(), |b| { - let mut rng = CryptoRng::seed_from_u64(123); - b.iter(|| choose_size_hinted(length, &mut rng)) - }); - c.bench_function( - format!("choose_stable_from_{length}_crypto").as_str(), + format!("choose_stable_from_{length}_{rng_name}").as_str(), |b| { - let mut rng = CryptoRng::seed_from_u64(123); + let mut rng = Rng::seed_from_u64(123); b.iter(|| choose_stable(length, &mut rng)) }, ); c.bench_function( - format!("choose_unhinted_from_{length}_crypto").as_str(), + format!("choose_unhinted_from_{length}_{rng_name}").as_str(), |b| { - let mut rng = CryptoRng::seed_from_u64(123); + let mut rng = Rng::seed_from_u64(123); b.iter(|| choose_unhinted(length, &mut rng)) }, ); c.bench_function( - format!("choose_windowed_from_{length}_crypto").as_str(), + format!("choose_windowed_from_{length}_{rng_name}").as_str(), |b| { - let mut rng = CryptoRng::seed_from_u64(123); + let mut rng = Rng::seed_from_u64(123); b.iter(|| choose_windowed(length, 7, &mut rng)) }, ); @@ -104,7 +84,8 @@ fn choose_windowed(max: usize, window_size: usize, rng: &mut R) -> Optio #[derive(Clone)] struct UnhintedIterator { - iter: I, } + iter: I, +} impl Iterator for UnhintedIterator { type Item = I::Item; @@ -115,7 +96,9 @@ impl Iterator for UnhintedIterator { #[derive(Clone)] struct WindowHintedIterator { - iter: I, window_size: usize, } + iter: I, + window_size: usize, +} impl Iterator for WindowHintedIterator { type Item = I::Item; From 88164491e8fd7fc20884acc33e6378bbdc3f0e33 Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Wed, 4 Jan 2023 17:02:45 +0000 Subject: [PATCH 24/26] Added Pcg64 benchmarks for seq_choose --- benches/seq_choose.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/benches/seq_choose.rs b/benches/seq_choose.rs index 4de6cb6992..7d01b766ec 100644 --- a/benches/seq_choose.rs +++ b/benches/seq_choose.rs @@ -21,6 +21,7 @@ criterion_main!(benches); pub fn bench(c: &mut Criterion) { bench_rng::(c, "ChaCha20"); bench_rng::(c, "Pcg32"); + bench_rng::(c, "Pcg64"); } fn bench_rng(c: &mut Criterion, rng_name: &'static str) { From d76ddb7298df8f2d093031cec13d88671a348585 Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Wed, 4 Jan 2023 17:07:07 +0000 Subject: [PATCH 25/26] Added TODO to coin_flipper --- src/seq/coin_flipper.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/seq/coin_flipper.rs b/src/seq/coin_flipper.rs index e754508979..77c18ded43 100644 --- a/src/seq/coin_flipper.rs +++ b/src/seq/coin_flipper.rs @@ -2,7 +2,7 @@ use crate::RngCore; pub(crate) struct CoinFlipper { pub rng: R, - chunk: u32, + chunk: u32, //TODO(opt): this should depend on RNG word size chunk_remaining: u32, } From a9aade63ecaa43ecdfef20a72169a4b7079dce1f Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Wed, 4 Jan 2023 17:19:39 +0000 Subject: [PATCH 26/26] Changed criterion settings in seq_choose --- benches/seq_choose.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/benches/seq_choose.rs b/benches/seq_choose.rs index 7d01b766ec..44b4bdf972 100644 --- a/benches/seq_choose.rs +++ b/benches/seq_choose.rs @@ -1,5 +1,3 @@ -use std::time::Duration; - // Copyright 2018-2022 Developers of the Rand project. // // Licensed under the Apache License, Version 2.0