Skip to content

Commit

Permalink
Improve performance of ReseedingRng
Browse files Browse the repository at this point in the history
Move the check if it is time to reseed out of the `try_reseed_if_necessaryImprove 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.`
  • Loading branch information
pitdicker committed Dec 16, 2017
1 parent 8323fc4 commit 906cf65
Showing 1 changed file with 71 additions and 60 deletions.
131 changes: 71 additions & 60 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,13 +43,14 @@ 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 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<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
}
}
Expand All @@ -59,101 +59,112 @@ impl<R: Rng, Rsdr: Reseeder<R>> ReseedingRng<R, Rsdr> {
/// 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.
///
/// 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 906cf65

Please sign in to comment.