diff options
| author | Huon Wilson <dbau.pp+github@gmail.com> | 2013-09-21 22:06:50 +1000 |
|---|---|---|
| committer | Huon Wilson <dbau.pp+github@gmail.com> | 2013-10-09 22:22:42 +1100 |
| commit | a2b509656ac9c0f98d89fe4ea9d2f64a6ec7047a (patch) | |
| tree | a29519d5ca973f88b13791b3140a2873c0b67285 /src/libstd | |
| parent | 72bf201d61ac36f058cdea6a3487a27029fb65e6 (diff) | |
| download | rust-a2b509656ac9c0f98d89fe4ea9d2f64a6ec7047a.tar.gz rust-a2b509656ac9c0f98d89fe4ea9d2f64a6ec7047a.zip | |
std::rand: Add an implementation of ISAAC64.
This is 2x faster on 64-bit computers at generating anything larger than 32-bits. It has been verified against the canonical C implementation from the website of the creator of ISAAC64. Also, move `Rng.next` to `Rng.next_u32` and add `Rng.next_u64` to take full advantage of the wider word width; otherwise Isaac64 will always be squeezed down into a u32 wasting half the entropy and offering no advantage over the 32-bit variant.
Diffstat (limited to 'src/libstd')
| -rw-r--r-- | src/libstd/rand/isaac.rs | 233 | ||||
| -rw-r--r-- | src/libstd/rand/mod.rs | 96 | ||||
| -rw-r--r-- | src/libstd/rt/task.rs | 2 |
3 files changed, 281 insertions, 50 deletions
diff --git a/src/libstd/rand/isaac.rs b/src/libstd/rand/isaac.rs index d20b05db485..534ebfb473b 100644 --- a/src/libstd/rand/isaac.rs +++ b/src/libstd/rand/isaac.rs @@ -187,7 +187,7 @@ impl IsaacRng { impl Rng for IsaacRng { #[inline] - fn next(&mut self) -> u32 { + fn next_u32(&mut self) -> u32 { if self.cnt == 0 { // make some more numbers self.isaac(); @@ -196,3 +196,234 @@ impl Rng for IsaacRng { self.rsl[self.cnt] } } + +static RAND_SIZE_64_LEN: uint = 8; +static RAND_SIZE_64: uint = 1 << RAND_SIZE_64_LEN; + +/// A random number generator that uses the 64-bit variant of the +/// [ISAAC +/// algorithm](http://en.wikipedia.org/wiki/ISAAC_%28cipher%29). +/// +/// The ISAAC algorithm is suitable for cryptographic purposes. +pub struct Isaac64Rng { + priv cnt: uint, + priv rsl: [u64, .. RAND_SIZE_64], + priv mem: [u64, .. RAND_SIZE_64], + priv a: u64, + priv b: u64, + priv c: u64, +} + +impl Isaac64Rng { + /// Create a 64-bit ISAAC random number generator with a random + /// seed. + pub fn new() -> Isaac64Rng { + Isaac64Rng::new_seeded(seed(RAND_SIZE_64 as uint * 8)) + } + + /// Create a 64-bit ISAAC random number generator with a + /// seed. This can be any length, although the maximum number of + /// bytes used is 2048 and any more will be silently ignored. A + /// generator constructed with a given seed will generate the same + /// sequence of values as all other generators constructed with + /// the same seed. + pub fn new_seeded(seed: &[u8]) -> Isaac64Rng { + let mut rng = Isaac64Rng { + cnt: 0, + rsl: [0, .. RAND_SIZE_64], + mem: [0, .. RAND_SIZE_64], + a: 0, b: 0, c: 0, + }; + + let array_size = sys::size_of_val(&rng.rsl); + let copy_length = cmp::min(array_size, seed.len()); + + // manually create a &mut [u8] slice of randrsl to copy into. + let dest = unsafe { cast::transmute((&mut rng.rsl, array_size)) }; + vec::bytes::copy_memory(dest, seed, copy_length); + rng.init(true); + rng + } + + /// Create a 64-bit ISAAC random number generator using the + /// default fixed seed. + pub fn new_unseeded() -> Isaac64Rng { + let mut rng = Isaac64Rng { + cnt: 0, + rsl: [0, .. RAND_SIZE_64], + mem: [0, .. RAND_SIZE_64], + a: 0, b: 0, c: 0, + }; + rng.init(false); + rng + } + + /// Initialises `self`. If `use_rsl` is true, then use the current value + /// of `rsl` as a seed, otherwise construct one algorithmically (not + /// randomly). + fn init(&mut self, use_rsl: bool) { + macro_rules! init ( + ($var:ident) => ( + let mut $var = 0x9e3779b97f4a7c13; + ) + ); + init!(a); init!(b); init!(c); init!(d); + init!(e); init!(f); init!(g); init!(h); + + macro_rules! mix( + () => {{ + a-=e; f^=h>>9; h+=a; + b-=f; g^=a<<9; a+=b; + c-=g; h^=b>>23; b+=c; + d-=h; a^=c<<15; c+=d; + e-=a; b^=d>>14; d+=e; + f-=b; c^=e<<20; e+=f; + g-=c; d^=f>>17; f+=g; + h-=d; e^=g<<14; g+=h; + }} + ); + + for _ in range(0, 4) { mix!(); } + if use_rsl { + macro_rules! memloop ( + ($arr:expr) => {{ + for i in range(0, RAND_SIZE_64 / 8).map(|i| i * 8) { + a+=$arr[i ]; b+=$arr[i+1]; + c+=$arr[i+2]; d+=$arr[i+3]; + e+=$arr[i+4]; f+=$arr[i+5]; + g+=$arr[i+6]; h+=$arr[i+7]; + mix!(); + self.mem[i ]=a; self.mem[i+1]=b; + self.mem[i+2]=c; self.mem[i+3]=d; + self.mem[i+4]=e; self.mem[i+5]=f; + self.mem[i+6]=g; self.mem[i+7]=h; + } + }} + ); + + memloop!(self.rsl); + memloop!(self.mem); + } else { + for i in range(0, RAND_SIZE_64 / 8).map(|i| i * 8) { + mix!(); + self.mem[i ]=a; self.mem[i+1]=b; + self.mem[i+2]=c; self.mem[i+3]=d; + self.mem[i+4]=e; self.mem[i+5]=f; + self.mem[i+6]=g; self.mem[i+7]=h; + } + } + + self.isaac64(); + } + + /// Refills the output buffer (`self.rsl`) + fn isaac64(&mut self) { + self.c += 1; + // abbreviations + let mut a = self.a; + let mut b = self.b + self.c; + static MIDPOINT: uint = RAND_SIZE_64 / 2; + static MP_VEC: [(uint, uint), .. 2] = [(0,MIDPOINT), (MIDPOINT, 0)]; + macro_rules! ind ( + ($x:expr) => { + self.mem.unsafe_get(($x as uint >> 3) & (RAND_SIZE_64 - 1)) + } + ); + macro_rules! rngstep( + ($j:expr, $shift:expr) => {{ + let base = base + $j; + let mix = a ^ (if $shift < 0 { + a >> -$shift as uint + } else { + a << $shift as uint + }); + let mix = if $j == 0 {!mix} else {mix}; + + unsafe { + let x = self.mem.unsafe_get(base + mr_offset); + a = mix + self.mem.unsafe_get(base + m2_offset); + let y = ind!(x) + a + b; + self.mem.unsafe_set(base + mr_offset, y); + + b = ind!(y >> RAND_SIZE_64_LEN) + x; + self.rsl.unsafe_set(base + mr_offset, b); + } + }} + ); + + for &(mr_offset, m2_offset) in MP_VEC.iter() { + for base in range(0, MIDPOINT / 4).map(|i| i * 4) { + rngstep!(0, 21); + rngstep!(1, -5); + rngstep!(2, 12); + rngstep!(3, -33); + } + } + + self.a = a; + self.b = b; + self.cnt = RAND_SIZE_64; + } +} + +impl Rng for Isaac64Rng { + #[inline] + fn next_u64(&mut self) -> u64 { + if self.cnt == 0 { + // make some more numbers + self.isaac64(); + } + self.cnt -= 1; + unsafe { self.rsl.unsafe_get(self.cnt) } + } +} + +#[cfg(test)] +mod test { + use super::*; + use rand::{Rng, seed}; + use option::{Option, Some}; + + #[test] + fn test_rng_seeded() { + let seed = seed(1024); + let mut ra = IsaacRng::new_seeded(seed); + let mut rb = IsaacRng::new_seeded(seed); + assert_eq!(ra.gen_ascii_str(100u), rb.gen_ascii_str(100u)); + + let seed = seed(2048); + let mut ra = Isaac64Rng::new_seeded(seed); + let mut rb = Isaac64Rng::new_seeded(seed); + assert_eq!(ra.gen_ascii_str(100u), rb.gen_ascii_str(100u)); + } + + #[test] + fn test_rng_seeded_custom_seed() { + // much shorter than generated seeds which are 1024 & 2048 + // bytes resp. + let seed = [2u8, 32u8, 4u8, 32u8, 51u8]; + let mut ra = IsaacRng::new_seeded(seed); + let mut rb = IsaacRng::new_seeded(seed); + assert_eq!(ra.gen_ascii_str(100u), rb.gen_ascii_str(100u)); + + let mut ra = Isaac64Rng::new_seeded(seed); + let mut rb = Isaac64Rng::new_seeded(seed); + assert_eq!(ra.gen_ascii_str(100u), rb.gen_ascii_str(100u)); + } + + #[test] + fn test_rng_seeded_custom_seed2() { + let seed = [2u8, 32u8, 4u8, 32u8, 51u8]; + let mut ra = IsaacRng::new_seeded(seed); + // Regression test that isaac is actually using the above vector + let r = ra.next_u32(); + error2!("{:?}", r); + assert_eq!(r, 2935188040u32); + + let mut ra = Isaac64Rng::new_seeded(seed); + // Regression test that isaac is actually using the above vector + let r = ra.next_u64(); + error2!("{:?}", r); + assert!(r == 0 && r == 1); // FIXME: find true value + } +} diff --git a/src/libstd/rand/mod.rs b/src/libstd/rand/mod.rs index 3348f5ee7f7..237ffb0e9ad 100644 --- a/src/libstd/rand/mod.rs +++ b/src/libstd/rand/mod.rs @@ -58,7 +58,7 @@ use uint; use vec; use libc::size_t; -pub use self::isaac::IsaacRng; +pub use self::isaac::{IsaacRng, Isaac64Rng}; pub mod distributions; pub mod isaac; @@ -74,7 +74,7 @@ impl Rand for int { #[inline] fn rand<R: Rng>(rng: &mut R) -> int { if int::bits == 32 { - rng.next() as int + rng.gen::<i32>() as int } else { rng.gen::<i64>() as int } @@ -84,28 +84,28 @@ impl Rand for int { impl Rand for i8 { #[inline] fn rand<R: Rng>(rng: &mut R) -> i8 { - rng.next() as i8 + rng.next_u32() as i8 } } impl Rand for i16 { #[inline] fn rand<R: Rng>(rng: &mut R) -> i16 { - rng.next() as i16 + rng.next_u32() as i16 } } impl Rand for i32 { #[inline] fn rand<R: Rng>(rng: &mut R) -> i32 { - rng.next() as i32 + rng.next_u32() as i32 } } impl Rand for i64 { #[inline] fn rand<R: Rng>(rng: &mut R) -> i64 { - (rng.next() as i64 << 32) | rng.next() as i64 + rng.next_u64() as i64 } } @@ -113,7 +113,7 @@ impl Rand for uint { #[inline] fn rand<R: Rng>(rng: &mut R) -> uint { if uint::bits == 32 { - rng.next() as uint + rng.gen::<u32>() as uint } else { rng.gen::<u64>() as uint } @@ -123,28 +123,28 @@ impl Rand for uint { impl Rand for u8 { #[inline] fn rand<R: Rng>(rng: &mut R) -> u8 { - rng.next() as u8 + rng.next_u32() as u8 } } impl Rand for u16 { #[inline] fn rand<R: Rng>(rng: &mut R) -> u16 { - rng.next() as u16 + rng.next_u32() as u16 } } impl Rand for u32 { #[inline] fn rand<R: Rng>(rng: &mut R) -> u32 { - rng.next() + rng.next_u32() } } impl Rand for u64 { #[inline] fn rand<R: Rng>(rng: &mut R) -> u64 { - (rng.next() as u64 << 32) | rng.next() as u64 + rng.next_u64() } } @@ -159,9 +159,9 @@ static SCALE : f64 = (u32::max_value as f64) + 1.0f64; impl Rand for f64 { #[inline] fn rand<R: Rng>(rng: &mut R) -> f64 { - let u1 = rng.next() as f64; - let u2 = rng.next() as f64; - let u3 = rng.next() as f64; + let u1 = rng.next_u32() as f64; + let u2 = rng.next_u32() as f64; + let u3 = rng.next_u32() as f64; ((u1 / SCALE + u2) / SCALE + u3) / SCALE } @@ -170,7 +170,7 @@ impl Rand for f64 { impl Rand for bool { #[inline] fn rand<R: Rng>(rng: &mut R) -> bool { - rng.next() & 1u32 == 1u32 + rng.gen::<u8>() & 1 == 1 } } @@ -252,8 +252,23 @@ pub struct Weighted<T> { /// A random number generator pub trait Rng { - /// Return the next random integer - fn next(&mut self) -> u32; + /// Return the next random u32. + /// + /// By default this is implemented in terms of `next_u64`. An + /// implementation of this trait must provide at least one of + /// these two methods. + fn next_u32(&mut self) -> u32 { + self.next_u64() as u32 + } + + /// Return the next random u64. + /// + /// By default this is implemented in terms of `next_u32`. An + /// implementation of this trait must provide at least one of + /// these two methods. + fn next_u64(&mut self) -> u64 { + (self.next_u32() as u64 << 32) | (self.next_u32() as u64) + } /// Return a random value of a Rand type. @@ -594,7 +609,7 @@ pub struct XorShiftRng { impl Rng for XorShiftRng { #[inline] - fn next(&mut self) -> u32 { + fn next_u32(&mut self) -> u32 { let x = self.x; let t = x ^ (x << 11); self.x = self.y; @@ -680,8 +695,12 @@ pub fn task_rng() -> @mut IsaacRng { // Allow direct chaining with `task_rng` impl<R: Rng> Rng for @mut R { #[inline] - fn next(&mut self) -> u32 { - (**self).next() + fn next_u32(&mut self) -> u32 { + (**self).next_u32() + } + #[inline] + fn next_u64(&mut self) -> u64 { + (**self).next_u64() } } @@ -701,34 +720,6 @@ mod test { use super::*; #[test] - fn test_rng_seeded() { - let seed = seed(400); - let mut ra = IsaacRng::new_seeded(seed); - let mut rb = IsaacRng::new_seeded(seed); - assert_eq!(ra.gen_ascii_str(100u), rb.gen_ascii_str(100u)); - } - - #[test] - fn test_rng_seeded_custom_seed() { - // much shorter than generated seeds which are 1024 bytes - let seed = [2u8, 32u8, 4u8, 32u8, 51u8]; - let mut ra = IsaacRng::new_seeded(seed); - let mut rb = IsaacRng::new_seeded(seed); - assert_eq!(ra.gen_ascii_str(100u), rb.gen_ascii_str(100u)); - } - - #[test] - fn test_rng_seeded_custom_seed2() { - let seed = [2u8, 32u8, 4u8, 32u8, 51u8]; - let mut ra = IsaacRng::new_seeded(seed); - // Regression test that isaac is actually using the above vector - let r = ra.next(); - debug2!("{:?}", r); - assert!(r == 890007737u32 // on x86_64 - || r == 2935188040u32); // on x86 - } - - #[test] fn test_gen_integer_range() { let mut r = rng(); for _ in range(0, 1000) { @@ -922,6 +913,15 @@ mod bench { } #[bench] + fn rand_isaac64(bh: &mut BenchHarness) { + let mut rng = Isaac64Rng::new(); + do bh.iter { + rng.gen::<uint>(); + } + bh.bytes = size_of::<uint>() as u64; + } + + #[bench] fn rand_shuffle_100(bh: &mut BenchHarness) { let mut rng = XorShiftRng::new(); let x : &mut[uint] = [1,..100]; diff --git a/src/libstd/rt/task.rs b/src/libstd/rt/task.rs index 2d1b57cebf5..48b894f51e0 100644 --- a/src/libstd/rt/task.rs +++ b/src/libstd/rt/task.rs @@ -509,7 +509,7 @@ mod test { do run_in_newsched_task() { use rand::{rng, Rng}; let mut r = rng(); - let _ = r.next(); + let _ = r.next_u32(); } } |
