Skip to content

Commit 67bb510

Browse files
authored
Merge pull request #303 from dhardy/blockrng
Blockrng: template over element type
2 parents 43ebf44 + 07f51bc commit 67bb510

File tree

9 files changed

+635
-439
lines changed

9 files changed

+635
-439
lines changed

benches/generators.rs

+28-4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ use test::{black_box, Bencher};
1212
use rand::{RngCore, Rng, SeedableRng, NewRng, StdRng, OsRng, JitterRng, EntropyRng};
1313
use rand::{XorShiftRng, Hc128Rng, IsaacRng, Isaac64Rng, ChaChaRng};
1414
use rand::reseeding::ReseedingRng;
15+
use rand::prng::hc128::Hc128Core;
16+
use rand::thread_rng;
1517

1618
macro_rules! gen_bytes {
1719
($fnn:ident, $gen:expr) => {
@@ -150,10 +152,13 @@ chacha_rounds!(gen_bytes_chacha12, gen_u32_chacha12, gen_u64_chacha12, 12);
150152
chacha_rounds!(gen_bytes_chacha20, gen_u32_chacha20, gen_u64_chacha20, 20);
151153

152154

155+
const RESEEDING_THRESHOLD: u64 = 1024*1024*1024; // something high enough to get
156+
// deterministic measurements
157+
153158
#[bench]
154159
fn reseeding_hc128_bytes(b: &mut Bencher) {
155-
let mut rng = ReseedingRng::new(Hc128Rng::new(),
156-
128*1024*1024,
160+
let mut rng = ReseedingRng::new(Hc128Core::new(),
161+
RESEEDING_THRESHOLD,
157162
EntropyRng::new());
158163
let mut buf = [0u8; BYTES_LEN];
159164
b.iter(|| {
@@ -169,8 +174,8 @@ macro_rules! reseeding_uint {
169174
($fnn:ident, $ty:ty) => {
170175
#[bench]
171176
fn $fnn(b: &mut Bencher) {
172-
let mut rng = ReseedingRng::new(Hc128Rng::new(),
173-
128*1024*1024,
177+
let mut rng = ReseedingRng::new(Hc128Core::new(),
178+
RESEEDING_THRESHOLD,
174179
EntropyRng::new());
175180
b.iter(|| {
176181
for _ in 0..RAND_BENCH_N {
@@ -184,3 +189,22 @@ macro_rules! reseeding_uint {
184189

185190
reseeding_uint!(reseeding_hc128_u32, u32);
186191
reseeding_uint!(reseeding_hc128_u64, u64);
192+
193+
194+
macro_rules! threadrng_uint {
195+
($fnn:ident, $ty:ty) => {
196+
#[bench]
197+
fn $fnn(b: &mut Bencher) {
198+
let mut rng = thread_rng();
199+
b.iter(|| {
200+
for _ in 0..RAND_BENCH_N {
201+
black_box(rng.gen::<$ty>());
202+
}
203+
});
204+
b.bytes = size_of::<$ty>() as u64 * RAND_BENCH_N;
205+
}
206+
}
207+
}
208+
209+
threadrng_uint!(thread_rng_u32, u32);
210+
threadrng_uint!(thread_rng_u64, u64);

rand-core/src/impls.rs

+173-2
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@
1919
//! to/from byte sequences, and since its purpose is reproducibility,
2020
//! non-reproducible sources (e.g. `OsRng`) need not bother with it.
2121
22+
use core::convert::AsRef;
2223
use core::intrinsics::transmute;
2324
use core::ptr::copy_nonoverlapping;
24-
use core::slice;
25+
use core::{fmt, slice};
2526
use core::cmp::min;
2627
use core::mem::size_of;
27-
use RngCore;
28+
use {RngCore, BlockRngCore, CryptoRng, SeedableRng, Error};
2829

2930
/// Implement `next_u64` via `next_u32`, little-endian order.
3031
pub fn next_u64_via_u32<R: RngCore + ?Sized>(rng: &mut R) -> u64 {
@@ -164,4 +165,174 @@ pub fn next_u64_via_fill<R: RngCore + ?Sized>(rng: &mut R) -> u64 {
164165
impl_uint_from_fill!(rng, u64, 8)
165166
}
166167

168+
/// Wrapper around PRNGs that implement [`BlockRngCore`] to keep a results
169+
/// buffer and offer the methods from [`RngCore`].
170+
///
171+
/// `BlockRng` has heavily optimized implementations of the [`RngCore`] methods
172+
/// reading values from the results buffer, as well as
173+
/// calling `BlockRngCore::generate` directly on the output array when
174+
/// `fill_bytes` / `try_fill_bytes` is called on a large array. These methods
175+
/// also handle the bookkeeping of when to generate a new batch of values.
176+
/// No generated values are ever thown away.
177+
///
178+
/// Currently `BlockRng` only implements `RngCore` for buffers which are slices
179+
/// of `u32` elements; this may be extended to other types in the future.
180+
///
181+
/// For easy initialization `BlockRng` also implements [`SeedableRng`].
182+
///
183+
/// [`BlockRngCore`]: ../BlockRngCore.t.html
184+
/// [`RngCore`]: ../RngCore.t.html
185+
/// [`SeedableRng`]: ../SeedableRng.t.html
186+
#[derive(Clone)]
187+
pub struct BlockRng<R: BlockRngCore + ?Sized> {
188+
pub results: R::Results,
189+
pub index: usize,
190+
pub core: R,
191+
}
192+
193+
// Custom Debug implementation that does not expose the contents of `results`.
194+
impl<R: BlockRngCore + fmt::Debug> fmt::Debug for BlockRng<R> {
195+
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
196+
fmt.debug_struct("BlockRng")
197+
.field("core", &self.core)
198+
.field("result_len", &self.results.as_ref().len())
199+
.field("index", &self.index)
200+
.finish()
201+
}
202+
}
203+
204+
impl<R: BlockRngCore<Item=u32>> RngCore for BlockRng<R>
205+
where <R as BlockRngCore>::Results: AsRef<[u32]>
206+
{
207+
#[inline(always)]
208+
fn next_u32(&mut self) -> u32 {
209+
if self.index >= self.results.as_ref().len() {
210+
self.core.generate(&mut self.results);
211+
self.index = 0;
212+
}
213+
214+
let value = self.results.as_ref()[self.index];
215+
self.index += 1;
216+
value
217+
}
218+
219+
#[inline(always)]
220+
fn next_u64(&mut self) -> u64 {
221+
let read_u64 = |results: &[u32], index| {
222+
if cfg!(any(target_arch = "x86", target_arch = "x86_64")) {
223+
// requires little-endian CPU supporting unaligned reads:
224+
unsafe { *(&results[index] as *const u32 as *const u64) }
225+
} else {
226+
let x = results[index] as u64;
227+
let y = results[index + 1] as u64;
228+
(y << 32) | x
229+
}
230+
};
231+
232+
let len = self.results.as_ref().len();
233+
234+
let index = self.index;
235+
if index < len-1 {
236+
self.index += 2;
237+
// Read an u64 from the current index
238+
read_u64(self.results.as_ref(), index)
239+
} else if index >= len {
240+
self.core.generate(&mut self.results);
241+
self.index = 2;
242+
read_u64(self.results.as_ref(), 0)
243+
} else {
244+
let x = self.results.as_ref()[len-1] as u64;
245+
self.core.generate(&mut self.results);
246+
self.index = 1;
247+
let y = self.results.as_ref()[0] as u64;
248+
(y << 32) | x
249+
}
250+
}
251+
252+
// As an optimization we try to write directly into the output buffer.
253+
// This is only enabled for little-endian platforms where unaligned writes
254+
// are known to be safe and fast.
255+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
256+
fn fill_bytes(&mut self, dest: &mut [u8]) {
257+
let mut filled = 0;
258+
259+
// Continue filling from the current set of results
260+
if self.index < self.results.as_ref().len() {
261+
let (consumed_u32, filled_u8) =
262+
fill_via_u32_chunks(&self.results.as_ref()[self.index..],
263+
dest);
264+
265+
self.index += consumed_u32;
266+
filled += filled_u8;
267+
}
268+
269+
let len_remainder =
270+
(dest.len() - filled) % (self.results.as_ref().len() * 4);
271+
let end_direct = dest.len() - len_remainder;
272+
273+
while filled < end_direct {
274+
let dest_u32: &mut R::Results = unsafe {
275+
::core::mem::transmute(dest[filled..].as_mut_ptr())
276+
};
277+
self.core.generate(dest_u32);
278+
filled += self.results.as_ref().len() * 4;
279+
}
280+
self.index = self.results.as_ref().len();
281+
282+
if len_remainder > 0 {
283+
self.core.generate(&mut self.results);
284+
let (consumed_u32, _) =
285+
fill_via_u32_chunks(&mut self.results.as_ref(),
286+
&mut dest[filled..]);
287+
288+
self.index = consumed_u32;
289+
}
290+
}
291+
292+
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
293+
fn fill_bytes(&mut self, dest: &mut [u8]) {
294+
let mut read_len = 0;
295+
while read_len < dest.len() {
296+
if self.index >= self.results.as_ref().len() {
297+
self.core.generate(&mut self.results);
298+
self.index = 0;
299+
}
300+
let (consumed_u32, filled_u8) =
301+
fill_via_u32_chunks(&self.results.as_ref()[self.index..],
302+
&mut dest[read_len..]);
303+
304+
self.index += consumed_u32;
305+
read_len += filled_u8;
306+
}
307+
}
308+
309+
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
310+
Ok(self.fill_bytes(dest))
311+
}
312+
}
313+
314+
impl<R: BlockRngCore + SeedableRng> SeedableRng for BlockRng<R> {
315+
type Seed = R::Seed;
316+
317+
fn from_seed(seed: Self::Seed) -> Self {
318+
let results_empty = R::Results::default();
319+
Self {
320+
core: R::from_seed(seed),
321+
index: results_empty.as_ref().len(), // generate on first use
322+
results: results_empty,
323+
}
324+
}
325+
326+
fn from_rng<RNG: RngCore>(rng: &mut RNG) -> Result<Self, Error> {
327+
let results_empty = R::Results::default();
328+
Ok(Self {
329+
core: R::from_rng(rng)?,
330+
index: results_empty.as_ref().len(), // generate on first use
331+
results: results_empty,
332+
})
333+
}
334+
}
335+
336+
impl<R: BlockRngCore + CryptoRng> CryptoRng for BlockRng<R> {}
337+
167338
// TODO: implement tests for the above

rand-core/src/lib.rs

+59-13
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,58 @@ pub trait RngCore {
162162
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error>;
163163
}
164164

165-
/// A marker trait for an `Rng` which may be considered for use in
166-
/// cryptography.
165+
/// A trait for RNGs which do not generate random numbers individually, but in
166+
/// blocks (typically `[u32; N]`). This technique is commonly used by
167+
/// cryptographic RNGs to improve performance.
168+
///
169+
/// Usage of this trait is optional, but provides two advantages:
170+
/// implementations only need to concern themselves with generation of the
171+
/// block, not the various `RngCore` methods (especially `fill_bytes`, where the
172+
/// optimal implementations are not trivial), and this allows `ReseedingRng` to
173+
/// perform periodic reseeding with very low overhead.
174+
///
175+
/// # Example
176+
///
177+
/// ```norun
178+
/// use rand_core::BlockRngCore;
179+
/// use rand_core::impls::BlockRng;
180+
///
181+
/// struct MyRngCore;
182+
///
183+
/// impl BlockRngCore for MyRngCore {
184+
/// type Results = [u32; 16];
185+
///
186+
/// fn generate(&mut self, results: &mut Self::Results) {
187+
/// unimplemented!()
188+
/// }
189+
/// }
190+
///
191+
/// impl SeedableRng for MyRngCore {
192+
/// type Seed = unimplemented!();
193+
/// fn from_seed(seed: Self::Seed) -> Self {
194+
/// unimplemented!()
195+
/// }
196+
/// }
197+
///
198+
/// // optionally, also implement CryptoRng for MyRngCore
199+
///
200+
/// // Final RNG.
201+
/// type MyRng = BlockRng<u32, MyRngCore>;
202+
/// ```
203+
pub trait BlockRngCore {
204+
/// Results element type, e.g. `u32`.
205+
type Item;
206+
207+
/// Results type. This is the 'block' an RNG implementing `BlockRngCore`
208+
/// generates, which will usually be an array like `[u32; 16]`.
209+
type Results: AsRef<[Self::Item]> + Default;
210+
211+
/// Generate a new block of results.
212+
fn generate(&mut self, results: &mut Self::Results);
213+
}
214+
215+
/// A marker trait used to indicate that an `RngCore` or `BlockRngCore`
216+
/// implementation is supposed to be cryptographically secure.
167217
///
168218
/// *Cryptographically secure generators*, also known as *CSPRNGs*, should
169219
/// satisfy an additional properties over other generators: given the first
@@ -182,7 +232,7 @@ pub trait RngCore {
182232
///
183233
/// Note also that use of a `CryptoRng` does not protect against other
184234
/// weaknesses such as seeding from a weak entropy source or leaking state.
185-
pub trait CryptoRng: RngCore {}
235+
pub trait CryptoRng {}
186236

187237
/// A random number generator that can be explicitly seeded.
188238
///
@@ -263,45 +313,41 @@ pub trait SeedableRng: Sized {
263313

264314

265315
impl<'a, R: RngCore + ?Sized> RngCore for &'a mut R {
266-
#[inline]
316+
#[inline(always)]
267317
fn next_u32(&mut self) -> u32 {
268318
(**self).next_u32()
269319
}
270320

271-
#[inline]
321+
#[inline(always)]
272322
fn next_u64(&mut self) -> u64 {
273323
(**self).next_u64()
274324
}
275325

276-
#[inline]
277326
fn fill_bytes(&mut self, dest: &mut [u8]) {
278327
(**self).fill_bytes(dest)
279328
}
280-
281-
#[inline]
329+
282330
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
283331
(**self).try_fill_bytes(dest)
284332
}
285333
}
286334

287335
#[cfg(any(feature="std", feature="alloc"))]
288336
impl<R: RngCore + ?Sized> RngCore for Box<R> {
289-
#[inline]
337+
#[inline(always)]
290338
fn next_u32(&mut self) -> u32 {
291339
(**self).next_u32()
292340
}
293341

294-
#[inline]
342+
#[inline(always)]
295343
fn next_u64(&mut self) -> u64 {
296344
(**self).next_u64()
297345
}
298346

299-
#[inline]
300347
fn fill_bytes(&mut self, dest: &mut [u8]) {
301348
(**self).fill_bytes(dest)
302349
}
303-
304-
#[inline]
350+
305351
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
306352
(**self).try_fill_bytes(dest)
307353
}

0 commit comments

Comments
 (0)