Skip to content

Commit

Permalink
Merge pull request #76 from pitdicker/reseeding_perf
Browse files Browse the repository at this point in the history
Reseeding perf
  • Loading branch information
dhardy authored Jan 1, 2018
2 parents 8990da2 + 14f02a2 commit d5d9c75
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 65 deletions.
38 changes: 38 additions & 0 deletions benches/generators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -102,3 +103,40 @@ fn init_jitter(b: &mut Bencher) {
black_box(JitterRng::new().unwrap());
});
}



#[bench]
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];
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(Hc128Rng::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_hc128_u32, u32);
reseeding_uint!(reseeding_hc128_u64, u64);
135 changes: 70 additions & 65 deletions src/reseeding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -32,8 +31,8 @@ const DEFAULT_GENERATION_THRESHOLD: u64 = 32 * 1024;
#[derive(Debug, Clone)]
pub struct ReseedingRng<R, Rsdr: Reseeder<R>> {
rng: R,
generation_threshold: u64,
bytes_generated: u64,
threshold: i64,
bytes_until_reseed: i64,
/// Controls the behaviour when reseeding the RNG.
pub reseeder: Rsdr,
}
Expand All @@ -44,116 +43,122 @@ impl<R: Rng, Rsdr: Reseeder<R>> ReseedingRng<R, Rsdr> {
/// # Arguments
///
/// * `rng`: the random number generator to use.
/// * `generation_threshold`: the number of bytes of entropy at 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, generation_threshold: u64, reseeder: Rsdr) -> ReseedingRng<R,Rsdr> {
pub fn new(rng: R, threshold: u64, reseeder: Rsdr) -> ReseedingRng<R,Rsdr> {
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
}
}

/// 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.
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 {
break; // give up trying to reseed
#[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) {
// TODO: log?
if e.kind.should_wait() {
// Delay reseeding
self.bytes_until_reseed = self.threshold >> 8;
} else if e.kind.should_retry() {
err_count += 1;
if err_count <= 5 { // arbitrary limit
continue; // retry immediately
}
} else {
break; // no reseeding
}
// give up trying to reseed
}
self.bytes_generated = 0;
break; // successfully reseeded, delayed, or given up.
}
}

/// Reseed the internal RNG if the number of bytes that have been
/// generated exceed the threshold.
///
/// 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(())
}
}


impl<R: Rng, Rsdr: Reseeder<R>> Rng for ReseedingRng<R, Rsdr> {
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<R: SeedableRng, Rsdr: Reseeder<R>> ReseedingRng<R, Rsdr> {
/// 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: <R as SeedableRng>::Seed) -> ReseedingRng<R, Rsdr> {
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
}
}
Expand Down

0 comments on commit d5d9c75

Please sign in to comment.