1
Fork 0

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.
This commit is contained in:
Huon Wilson 2013-09-21 22:06:50 +10:00
parent 72bf201d61
commit a2b509656a
8 changed files with 295 additions and 64 deletions

View file

@ -1533,7 +1533,7 @@ mod tests {
let mut r = rng(); let mut r = rng();
let mut bitv = 0 as uint; let mut bitv = 0 as uint;
do b.iter { do b.iter {
bitv |= (1 << ((r.next() as uint) % uint::bits)); bitv |= (1 << ((r.next_u32() as uint) % uint::bits));
} }
} }
@ -1542,7 +1542,7 @@ mod tests {
let mut r = rng(); let mut r = rng();
let mut bitv = SmallBitv::new(uint::bits); let mut bitv = SmallBitv::new(uint::bits);
do b.iter { do b.iter {
bitv.set((r.next() as uint) % uint::bits, true); bitv.set((r.next_u32() as uint) % uint::bits, true);
} }
} }
@ -1551,7 +1551,7 @@ mod tests {
let mut r = rng(); let mut r = rng();
let mut bitv = BigBitv::new(~[0]); let mut bitv = BigBitv::new(~[0]);
do b.iter { do b.iter {
bitv.set((r.next() as uint) % uint::bits, true); bitv.set((r.next_u32() as uint) % uint::bits, true);
} }
} }
@ -1562,7 +1562,7 @@ mod tests {
storage.grow(BENCH_BITS / uint::bits, &0u); storage.grow(BENCH_BITS / uint::bits, &0u);
let mut bitv = BigBitv::new(storage); let mut bitv = BigBitv::new(storage);
do b.iter { do b.iter {
bitv.set((r.next() as uint) % BENCH_BITS, true); bitv.set((r.next_u32() as uint) % BENCH_BITS, true);
} }
} }
@ -1571,7 +1571,7 @@ mod tests {
let mut r = rng(); let mut r = rng();
let mut bitv = Bitv::new(BENCH_BITS, false); let mut bitv = Bitv::new(BENCH_BITS, false);
do b.iter { do b.iter {
bitv.set((r.next() as uint) % BENCH_BITS, true); bitv.set((r.next_u32() as uint) % BENCH_BITS, true);
} }
} }
@ -1580,7 +1580,7 @@ mod tests {
let mut r = rng(); let mut r = rng();
let mut bitv = Bitv::new(uint::bits, false); let mut bitv = Bitv::new(uint::bits, false);
do b.iter { do b.iter {
bitv.set((r.next() as uint) % uint::bits, true); bitv.set((r.next_u32() as uint) % uint::bits, true);
} }
} }
@ -1589,7 +1589,7 @@ mod tests {
let mut r = rng(); let mut r = rng();
let mut bitv = BitvSet::new(); let mut bitv = BitvSet::new();
do b.iter { do b.iter {
bitv.insert((r.next() as uint) % uint::bits); bitv.insert((r.next_u32() as uint) % uint::bits);
} }
} }
@ -1598,7 +1598,7 @@ mod tests {
let mut r = rng(); let mut r = rng();
let mut bitv = BitvSet::new(); let mut bitv = BitvSet::new();
do b.iter { do b.iter {
bitv.insert((r.next() as uint) % BENCH_BITS); bitv.insert((r.next_u32() as uint) % BENCH_BITS);
} }
} }

View file

@ -187,7 +187,7 @@ impl IsaacRng {
impl Rng for IsaacRng { impl Rng for IsaacRng {
#[inline] #[inline]
fn next(&mut self) -> u32 { fn next_u32(&mut self) -> u32 {
if self.cnt == 0 { if self.cnt == 0 {
// make some more numbers // make some more numbers
self.isaac(); self.isaac();
@ -196,3 +196,234 @@ impl Rng for IsaacRng {
self.rsl[self.cnt] 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
}
}

View file

@ -58,7 +58,7 @@ use uint;
use vec; use vec;
use libc::size_t; use libc::size_t;
pub use self::isaac::IsaacRng; pub use self::isaac::{IsaacRng, Isaac64Rng};
pub mod distributions; pub mod distributions;
pub mod isaac; pub mod isaac;
@ -74,7 +74,7 @@ impl Rand for int {
#[inline] #[inline]
fn rand<R: Rng>(rng: &mut R) -> int { fn rand<R: Rng>(rng: &mut R) -> int {
if int::bits == 32 { if int::bits == 32 {
rng.next() as int rng.gen::<i32>() as int
} else { } else {
rng.gen::<i64>() as int rng.gen::<i64>() as int
} }
@ -84,28 +84,28 @@ impl Rand for int {
impl Rand for i8 { impl Rand for i8 {
#[inline] #[inline]
fn rand<R: Rng>(rng: &mut R) -> i8 { fn rand<R: Rng>(rng: &mut R) -> i8 {
rng.next() as i8 rng.next_u32() as i8
} }
} }
impl Rand for i16 { impl Rand for i16 {
#[inline] #[inline]
fn rand<R: Rng>(rng: &mut R) -> i16 { fn rand<R: Rng>(rng: &mut R) -> i16 {
rng.next() as i16 rng.next_u32() as i16
} }
} }
impl Rand for i32 { impl Rand for i32 {
#[inline] #[inline]
fn rand<R: Rng>(rng: &mut R) -> i32 { fn rand<R: Rng>(rng: &mut R) -> i32 {
rng.next() as i32 rng.next_u32() as i32
} }
} }
impl Rand for i64 { impl Rand for i64 {
#[inline] #[inline]
fn rand<R: Rng>(rng: &mut R) -> i64 { 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] #[inline]
fn rand<R: Rng>(rng: &mut R) -> uint { fn rand<R: Rng>(rng: &mut R) -> uint {
if uint::bits == 32 { if uint::bits == 32 {
rng.next() as uint rng.gen::<u32>() as uint
} else { } else {
rng.gen::<u64>() as uint rng.gen::<u64>() as uint
} }
@ -123,28 +123,28 @@ impl Rand for uint {
impl Rand for u8 { impl Rand for u8 {
#[inline] #[inline]
fn rand<R: Rng>(rng: &mut R) -> u8 { fn rand<R: Rng>(rng: &mut R) -> u8 {
rng.next() as u8 rng.next_u32() as u8
} }
} }
impl Rand for u16 { impl Rand for u16 {
#[inline] #[inline]
fn rand<R: Rng>(rng: &mut R) -> u16 { fn rand<R: Rng>(rng: &mut R) -> u16 {
rng.next() as u16 rng.next_u32() as u16
} }
} }
impl Rand for u32 { impl Rand for u32 {
#[inline] #[inline]
fn rand<R: Rng>(rng: &mut R) -> u32 { fn rand<R: Rng>(rng: &mut R) -> u32 {
rng.next() rng.next_u32()
} }
} }
impl Rand for u64 { impl Rand for u64 {
#[inline] #[inline]
fn rand<R: Rng>(rng: &mut R) -> u64 { 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 { impl Rand for f64 {
#[inline] #[inline]
fn rand<R: Rng>(rng: &mut R) -> f64 { fn rand<R: Rng>(rng: &mut R) -> f64 {
let u1 = rng.next() as f64; let u1 = rng.next_u32() as f64;
let u2 = rng.next() as f64; let u2 = rng.next_u32() as f64;
let u3 = rng.next() as f64; let u3 = rng.next_u32() as f64;
((u1 / SCALE + u2) / SCALE + u3) / SCALE ((u1 / SCALE + u2) / SCALE + u3) / SCALE
} }
@ -170,7 +170,7 @@ impl Rand for f64 {
impl Rand for bool { impl Rand for bool {
#[inline] #[inline]
fn rand<R: Rng>(rng: &mut R) -> bool { 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 /// A random number generator
pub trait Rng { pub trait Rng {
/// Return the next random integer /// Return the next random u32.
fn next(&mut self) -> 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. /// Return a random value of a Rand type.
@ -594,7 +609,7 @@ pub struct XorShiftRng {
impl Rng for XorShiftRng { impl Rng for XorShiftRng {
#[inline] #[inline]
fn next(&mut self) -> u32 { fn next_u32(&mut self) -> u32 {
let x = self.x; let x = self.x;
let t = x ^ (x << 11); let t = x ^ (x << 11);
self.x = self.y; self.x = self.y;
@ -680,8 +695,12 @@ pub fn task_rng() -> @mut IsaacRng {
// Allow direct chaining with `task_rng` // Allow direct chaining with `task_rng`
impl<R: Rng> Rng for @mut R { impl<R: Rng> Rng for @mut R {
#[inline] #[inline]
fn next(&mut self) -> u32 { fn next_u32(&mut self) -> u32 {
(**self).next() (**self).next_u32()
}
#[inline]
fn next_u64(&mut self) -> u64 {
(**self).next_u64()
} }
} }
@ -700,34 +719,6 @@ mod test {
use option::{Option, Some}; use option::{Option, Some};
use super::*; 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] #[test]
fn test_gen_integer_range() { fn test_gen_integer_range() {
let mut r = rng(); let mut r = rng();
@ -921,6 +912,15 @@ mod bench {
bh.bytes = size_of::<uint>() as u64; bh.bytes = size_of::<uint>() as u64;
} }
#[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] #[bench]
fn rand_shuffle_100(bh: &mut BenchHarness) { fn rand_shuffle_100(bh: &mut BenchHarness) {
let mut rng = XorShiftRng::new(); let mut rng = XorShiftRng::new();

View file

@ -509,7 +509,7 @@ mod test {
do run_in_newsched_task() { do run_in_newsched_task() {
use rand::{rng, Rng}; use rand::{rng, Rng};
let mut r = rng(); let mut r = rng();
let _ = r.next(); let _ = r.next_u32();
} }
} }

View file

@ -109,7 +109,7 @@ fn main() {
let mut rng = std::rand::IsaacRng::new_seeded([1, 1, 1, 1, 1, 1, 1]); let mut rng = std::rand::IsaacRng::new_seeded([1, 1, 1, 1, 1, 1, 1]);
let mut set = HashSet::new(); let mut set = HashSet::new();
while set.len() != n_keys { while set.len() != n_keys {
let next = rng.next() as uint; let next = rng.gen();
if set.insert(next) { if set.insert(next) {
rand.push(next); rand.push(next);
} }

View file

@ -60,7 +60,7 @@ impl Results {
let mut set = f(); let mut set = f();
do timed(&mut self.random_ints) { do timed(&mut self.random_ints) {
for _ in range(0, num_keys) { for _ in range(0, num_keys) {
set.insert((rng.next() as uint) % rand_cap); set.insert(rng.gen::<uint>() % rand_cap);
} }
} }
} }
@ -102,7 +102,7 @@ impl Results {
let mut set = f(); let mut set = f();
do timed(&mut self.random_strings) { do timed(&mut self.random_strings) {
for _ in range(0, num_keys) { for _ in range(0, num_keys) {
let s = (rng.next() as uint).to_str(); let s = rng.gen::<uint>().to_str();
set.insert(s); set.insert(s);
} }
} }

View file

@ -76,7 +76,7 @@ fn make_random_fasta(wr: @io::Writer,
wr.write_line(~">" + id + " " + desc); wr.write_line(~">" + id + " " + desc);
let mut rng = rand::rng(); let mut rng = rand::rng();
let rng = @mut MyRandom { let rng = @mut MyRandom {
last: rng.next() last: rng.gen()
}; };
let mut op: ~str = ~""; let mut op: ~str = ~"";
for _ in range(0u, n as uint) { for _ in range(0u, n as uint) {

View file

@ -69,8 +69,8 @@ pub fn main() {
let mut rng = rand::rng(); let mut rng = rand::rng();
for f in fns.iter() { for f in fns.iter() {
let f = *f; let f = *f;
let sz = rng.next() % 256u32 + 256u32; let sz = rng.gen::<u32>() % 256u32 + 256u32;
let frame_backoff = rng.next() % 10u32 + 1u32; let frame_backoff = rng.gen::<u32>() % 10u32 + 1u32;
task::try(|| runtest(f, frame_backoff) ); task::try(|| runtest(f, frame_backoff) );
} }
} }