-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reseeding perf #76
Reseeding perf #76
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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<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, | ||
} | ||
|
@@ -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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe "the number of [generated] bytes" is correct English; amount is typically used for "uncountable" things (e.g. water, money, food). But I'm not really fussed (I know I accepted something similar recently anyway). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please keep correcting my English, It is not my first language and it is better if the language in the documentation is correct. |
||
/// * `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 | ||
} | ||
} | ||
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This sets I admit that the logic of this function is weird but I guess something like this is useful. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hee, you're right. |
||
} 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 | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it make more sense to benchmark the PRNG we're interested in (ISAAC or HC128)? Especially given that your
BlockRng
idea integrates tighter with the PRNG algorithm.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With HC-128 the overhead of reseeding is much larger:
And before this PR:
This makes sense, because the overhead of checking and indexing in the results array makes up 20~40%. With
ReseedingRng
that percentage gets doubled because it also does checks for reseeding.You are right, this benchmark is testing something nonsensical. Reseeding Xorshift ?!. I don't think it matters much because they both show the overhead of
ReseedingRng
, but I'll change it.