From 402e673eb3c41f69d017e30bdc1dad65f9edbbfd Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 16 Dec 2017 19:50:43 +0100 Subject: [PATCH 1/4] Add benchmarks for `ReseedingRng` --- benches/generators.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/benches/generators.rs b/benches/generators.rs index ec603d198cf..1c0262e7d56 100644 --- a/benches/generators.rs +++ b/benches/generators.rs @@ -11,6 +11,7 @@ use test::{black_box, Bencher}; use rand::{Rng, NewSeeded, Sample, SeedableRng, StdRng, OsRng, JitterRng}; use rand::prng::*; +use rand::reseeding::{ReseedingRng, ReseedWithNew}; macro_rules! gen_bytes { ($fnn:ident, $gen:ident) => { @@ -102,3 +103,40 @@ fn init_jitter(b: &mut Bencher) { black_box(JitterRng::new().unwrap()); }); } + + + +#[bench] +fn reseeding_xorshift_bytes(b: &mut Bencher) { + let mut rng = ReseedingRng::new(XorShiftRng::new().unwrap(), + 128*1024*1024*1024, + ReseedWithNew); + let mut buf = [0u8; BYTES_LEN]; + b.iter(|| { + for _ in 0..RAND_BENCH_N { + rng.fill_bytes(&mut buf); + black_box(buf); + } + }); + b.bytes = BYTES_LEN as u64 * RAND_BENCH_N; +} + +macro_rules! reseeding_uint { + ($fnn:ident, $ty:ty) => { + #[bench] + fn $fnn(b: &mut Bencher) { + let mut rng = ReseedingRng::new(XorShiftRng::new().unwrap(), + 128*1024*1024*1024, + ReseedWithNew); + b.iter(|| { + for _ in 0..RAND_BENCH_N { + black_box(rng.gen::<$ty>()); + } + }); + b.bytes = size_of::<$ty>() as u64 * RAND_BENCH_N; + } + } +} + +reseeding_uint!(reseeding_xorshift_u32, u32); +reseeding_uint!(reseeding_xorshift_u64, u64); From 4d1032893acaeb9483ff5b89d30f42e537d61b49 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 16 Dec 2017 19:50:48 +0100 Subject: [PATCH 2/4] Improve performance of `ReseedingRng` * Move the check if it is time to reseed out of the `try_reseed_if_necessary` and make sure that function does not get inlined. * Invert the counter direction. This way we can compare against 0 instead of `self.threshold` * Doing the reseed check after generating a value turns out to be a bit faster.` --- src/reseeding.rs | 131 +++++++++++++++++++++++++---------------------- 1 file changed, 71 insertions(+), 60 deletions(-) diff --git a/src/reseeding.rs b/src/reseeding.rs index 5d95910825f..3187eefd3f1 100644 --- a/src/reseeding.rs +++ b/src/reseeding.rs @@ -11,14 +11,13 @@ //! A wrapper around another RNG that reseeds it after it //! generates a certain number of random bytes. -use core::cmp::max; use {Rng, SeedableRng, Error, ErrorKind}; #[cfg(feature="std")] use NewSeeded; /// How many bytes of entropy the underling RNG is allowed to generate /// before it is reseeded -const DEFAULT_GENERATION_THRESHOLD: u64 = 32 * 1024; +const DEFAULT_RESEEDING_THRESHOLD: i64 = 32 * 1024; /// A wrapper around any RNG which reseeds the underlying RNG after it /// has generated a certain number of random bytes. @@ -32,8 +31,8 @@ const DEFAULT_GENERATION_THRESHOLD: u64 = 32 * 1024; #[derive(Debug, Clone)] pub struct ReseedingRng> { rng: R, - generation_threshold: u64, - bytes_generated: u64, + threshold: i64, + bytes_until_reseed: i64, /// Controls the behaviour when reseeding the RNG. pub reseeder: Rsdr, } @@ -44,13 +43,14 @@ impl> ReseedingRng { /// # Arguments /// /// * `rng`: the random number generator to use. - /// * `generation_threshold`: the number of bytes of entropy at which to reseed the RNG. + /// * `threshold`: the amount of generated bytes after which to reseed the RNG. /// * `reseeder`: the reseeding object to use. - pub fn new(rng: R, generation_threshold: u64, reseeder: Rsdr) -> ReseedingRng { + pub fn new(rng: R, threshold: u64, reseeder: Rsdr) -> ReseedingRng { + assert!(threshold <= ::core::i64::MAX as u64); ReseedingRng { rng: rng, - generation_threshold: generation_threshold, - bytes_generated: 0, + threshold: threshold as i64, + bytes_until_reseed: threshold as i64, reseeder: reseeder } } @@ -59,33 +59,31 @@ impl> ReseedingRng { /// generated exceed the threshold. /// /// On error, this may delay reseeding or not reseed at all. - pub fn reseed_if_necessary(&mut self) { - if self.bytes_generated >= self.generation_threshold { - let mut err_count = 0; - loop { - if let Err(e) = self.reseeder.reseed(&mut self.rng) { - // TODO: log? - if e.kind.should_wait() { - // Delay reseeding - let delay = max(self.generation_threshold >> 8, self.bytes_generated); - self.bytes_generated -= delay; - break; - } else if e.kind.should_retry() { - if err_count > 4 { // arbitrary limit - // TODO: log details & cause? - break; // give up trying to reseed - } - err_count += 1; - continue; // immediate retry - } else { + #[inline(never)] + pub fn reseed(&mut self) { + let mut err_count = 0; + loop { + if let Err(e) = self.reseeder.reseed(&mut self.rng) { + // TODO: log? + if e.kind.should_wait() { + // Delay reseeding + self.bytes_until_reseed = self.threshold >> 8; + break; + } else if e.kind.should_retry() { + if err_count > 4 { // arbitrary limit + // TODO: log details & cause? break; // give up trying to reseed } + err_count += 1; + continue; // immediate retry } else { - break; // no reseeding + break; // give up trying to reseed } + } else { + break; // no reseeding } - self.bytes_generated = 0; } + self.bytes_until_reseed = self.threshold; } /// Reseed the internal RNG if the number of bytes that have been /// generated exceed the threshold. @@ -93,21 +91,20 @@ impl> ReseedingRng { /// If reseeding fails, return an error with the original cause. Note that /// if the cause has a permanent failure, we report a transient error and /// skip reseeding. - pub fn try_reseed_if_necessary(&mut self) -> Result<(), Error> { - if self.bytes_generated >= self.generation_threshold { - if let Err(err) = self.reseeder.reseed(&mut self.rng) { - let newkind = match err.kind { - a @ ErrorKind::NotReady => a, - b @ ErrorKind::Transient => b, - _ => { - self.bytes_generated = 0; // skip reseeding - ErrorKind::Transient - } - }; - return Err(Error::with_cause(newkind, "reseeding failed", err)); - } - self.bytes_generated = 0; + #[inline(never)] + pub fn try_reseed(&mut self) -> Result<(), Error> { + if let Err(err) = self.reseeder.reseed(&mut self.rng) { + let newkind = match err.kind { + a @ ErrorKind::NotReady => a, + b @ ErrorKind::Transient => b, + _ => { + self.bytes_until_reseed = self.threshold; // skip reseeding + ErrorKind::Transient + } + }; + return Err(Error::with_cause(newkind, "reseeding failed", err)); } + self.bytes_until_reseed = self.threshold; Ok(()) } } @@ -115,45 +112,59 @@ impl> ReseedingRng { impl> Rng for ReseedingRng { fn next_u32(&mut self) -> u32 { - self.reseed_if_necessary(); - self.bytes_generated += 4; - self.rng.next_u32() + let value = self.rng.next_u32(); + self.bytes_until_reseed -= 4; + if self.bytes_until_reseed <= 0 { + self.reseed(); + } + value } fn next_u64(&mut self) -> u64 { - self.reseed_if_necessary(); - self.bytes_generated += 8; - self.rng.next_u64() + let value = self.rng.next_u64(); + self.bytes_until_reseed -= 8; + if self.bytes_until_reseed <= 0 { + self.reseed(); + } + value } #[cfg(feature = "i128_support")] fn next_u128(&mut self) -> u128 { - self.reseed_if_necessary(); - self.bytes_generated += 16; - self.rng.next_u128() + let value = self.rng.next_u128(); + self.bytes_until_reseed -= 16; + if self.bytes_until_reseed <= 0 { + self.reseed(); + } + value } fn fill_bytes(&mut self, dest: &mut [u8]) { - self.reseed_if_necessary(); - self.bytes_generated += dest.len() as u64; self.rng.fill_bytes(dest); + self.bytes_until_reseed -= dest.len() as i64; + if self.bytes_until_reseed <= 0 { + self.reseed(); + } } fn try_fill(&mut self, dest: &mut [u8]) -> Result<(), Error> { - self.try_reseed_if_necessary()?; - self.bytes_generated += dest.len() as u64; - self.rng.try_fill(dest) + self.rng.try_fill(dest)?; + self.bytes_until_reseed -= dest.len() as i64; + if self.bytes_until_reseed <= 0 { + self.try_reseed()?; + } + Ok(()) } } impl> ReseedingRng { /// Create a new `ReseedingRng` from the given reseeder and - /// seed. This uses a default value for `generation_threshold`. + /// seed. This uses a default value for `threshold`. pub fn from_reseeder(rsdr: Rsdr, seed: ::Seed) -> ReseedingRng { ReseedingRng { rng: SeedableRng::from_seed(seed), - generation_threshold: DEFAULT_GENERATION_THRESHOLD, - bytes_generated: 0, + threshold: DEFAULT_RESEEDING_THRESHOLD, + bytes_until_reseed: DEFAULT_RESEEDING_THRESHOLD, reseeder: rsdr } } From 07717bcafde6ae31db1b052e48a5da8c048e9378 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 31 Dec 2017 13:29:46 +0100 Subject: [PATCH 3/4] Simplify reseeding erro logic --- src/reseeding.rs | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/reseeding.rs b/src/reseeding.rs index 3187eefd3f1..fd48051fe20 100644 --- a/src/reseeding.rs +++ b/src/reseeding.rs @@ -55,12 +55,11 @@ impl> ReseedingRng { } } - /// Reseed the internal RNG if the number of bytes that have been - /// generated exceed the threshold. - /// + /// Reseed the internal RNG. /// On error, this may delay reseeding or not reseed at all. #[inline(never)] pub fn reseed(&mut self) { + self.bytes_until_reseed = self.threshold; let mut err_count = 0; loop { if let Err(e) = self.reseeder.reseed(&mut self.rng) { @@ -68,23 +67,18 @@ impl> ReseedingRng { if e.kind.should_wait() { // Delay reseeding self.bytes_until_reseed = self.threshold >> 8; - break; } else if e.kind.should_retry() { - if err_count > 4 { // arbitrary limit - // TODO: log details & cause? - break; // give up trying to reseed - } err_count += 1; - continue; // immediate retry - } else { - break; // give up trying to reseed + if err_count <= 5 { // arbitrary limit + continue; // retry immediately + } } - } else { - break; // no reseeding + // give up trying to reseed } + break; // successfully reseeded, delayed, or given up. } - self.bytes_until_reseed = self.threshold; } + /// Reseed the internal RNG if the number of bytes that have been /// generated exceed the threshold. /// From 14f02a226390a02fa2b42146c3c0c03243da80fa Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 31 Dec 2017 16:34:34 +0100 Subject: [PATCH 4/4] Benchmark reseeding HC-128 instead of Xorshift --- benches/generators.rs | 10 +++++----- src/reseeding.rs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/benches/generators.rs b/benches/generators.rs index 1c0262e7d56..b01a1725ceb 100644 --- a/benches/generators.rs +++ b/benches/generators.rs @@ -107,8 +107,8 @@ fn init_jitter(b: &mut Bencher) { #[bench] -fn reseeding_xorshift_bytes(b: &mut Bencher) { - let mut rng = ReseedingRng::new(XorShiftRng::new().unwrap(), +fn reseeding_hc128_bytes(b: &mut Bencher) { + let mut rng = ReseedingRng::new(Hc128Rng::new().unwrap(), 128*1024*1024*1024, ReseedWithNew); let mut buf = [0u8; BYTES_LEN]; @@ -125,7 +125,7 @@ macro_rules! reseeding_uint { ($fnn:ident, $ty:ty) => { #[bench] fn $fnn(b: &mut Bencher) { - let mut rng = ReseedingRng::new(XorShiftRng::new().unwrap(), + let mut rng = ReseedingRng::new(Hc128Rng::new().unwrap(), 128*1024*1024*1024, ReseedWithNew); b.iter(|| { @@ -138,5 +138,5 @@ macro_rules! reseeding_uint { } } -reseeding_uint!(reseeding_xorshift_u32, u32); -reseeding_uint!(reseeding_xorshift_u64, u64); +reseeding_uint!(reseeding_hc128_u32, u32); +reseeding_uint!(reseeding_hc128_u64, u64); diff --git a/src/reseeding.rs b/src/reseeding.rs index fd48051fe20..8af604f697e 100644 --- a/src/reseeding.rs +++ b/src/reseeding.rs @@ -43,7 +43,7 @@ impl> ReseedingRng { /// # Arguments /// /// * `rng`: the random number generator to use. - /// * `threshold`: the amount of generated bytes after which to reseed the RNG. + /// * `threshold`: the number of generated bytes after which to reseed the RNG. /// * `reseeder`: the reseeding object to use. pub fn new(rng: R, threshold: u64, reseeder: Rsdr) -> ReseedingRng { assert!(threshold <= ::core::i64::MAX as u64);