Skip to content

Commit 30266a7

Browse files
committed
core: a pure Rust implementation of the ISAAC RNG.
This replaces the wrapper around the runtime RNG with a pure Rust implementation of the same algorithm. This is faster (up to 5x), and is hopefully safer. There is still much room for optimisation: testing by summing 100,000,000 random `u32`s indicates this is about 40-50% slower than the pure C implementation (running as standalone executable, not in the runtime).
1 parent dbcc3fe commit 30266a7

File tree

1 file changed

+202
-46
lines changed

1 file changed

+202
-46
lines changed

src/libcore/rand.rs

Lines changed: 202 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -207,20 +207,13 @@ impl<T: Rand> Rand for @T {
207207
fn rand<R: Rng>(rng: &R) -> @T { @rng.gen() }
208208
}
209209

210-
#[allow(non_camel_case_types)] // runtime type
211-
pub enum rust_rng {}
212-
213210
#[abi = "cdecl"]
214211
pub mod rustrt {
215212
use libc::size_t;
216-
use super::rust_rng;
217213

218214
pub extern {
219215
unsafe fn rand_seed_size() -> size_t;
220216
unsafe fn rand_gen_seed(buf: *mut u8, sz: size_t);
221-
unsafe fn rand_new_seeded(buf: *u8, sz: size_t) -> *rust_rng;
222-
unsafe fn rand_next(rng: *rust_rng) -> u32;
223-
unsafe fn rand_free(rng: *rust_rng);
224217
}
225218
}
226219

@@ -566,66 +559,179 @@ pub fn rng() -> IsaacRng {
566559
IsaacRng::new()
567560
}
568561

569-
pub struct IsaacRng {
570-
priv rng: *rust_rng,
571-
}
562+
static RAND_SIZE_LEN: u32 = 8;
563+
static RAND_SIZE: u32 = 1 << RAND_SIZE_LEN;
572564

573-
impl Drop for IsaacRng {
574-
fn finalize(&self) {
575-
unsafe {
576-
rustrt::rand_free(self.rng);
577-
}
578-
}
565+
/// A random number generator that uses the [ISAAC
566+
/// algorithm](http://en.wikipedia.org/wiki/ISAAC_%28cipher%29).
567+
pub struct IsaacRng {
568+
priv mut cnt: u32,
569+
priv mut rsl: [u32, .. RAND_SIZE],
570+
priv mut mem: [u32, .. RAND_SIZE],
571+
priv mut a: u32,
572+
priv mut b: u32,
573+
priv mut c: u32
579574
}
580575

581576
pub impl IsaacRng {
582-
priv fn from_rust_rng(rng: *rust_rng) -> IsaacRng {
583-
IsaacRng {
584-
rng: rng
585-
}
586-
}
587-
588-
/// Create an ISAAC random number generator with a system specified seed
577+
/// Create an ISAAC random number generator with a random seed.
589578
fn new() -> IsaacRng {
590579
IsaacRng::new_seeded(seed())
591580
}
592581

593-
/**
594-
* Create a random number generator using the specified seed. A generator
595-
* constructed with a given seed will generate the same sequence of values as
596-
* all other generators constructed with the same seed. The seed may be any
597-
* length.
598-
*/
582+
/// Create an ISAAC random number generator with a seed. This can be any
583+
/// length, although the maximum number of bytes used is 1024 and any more
584+
/// will be silently ignored. A generator constructed with a given seed
585+
/// will generate the same sequence of values as all other generators
586+
/// constructed with the same seed.
599587
fn new_seeded(seed: &[u8]) -> IsaacRng {
600-
unsafe {
601-
do vec::as_imm_buf(seed) |p, sz| {
602-
IsaacRng::from_rust_rng(rustrt::rand_new_seeded(p, sz as size_t))
588+
let mut rng = IsaacRng {
589+
cnt: 0,
590+
rsl: [0, .. RAND_SIZE],
591+
mem: [0, .. RAND_SIZE],
592+
a: 0, b: 0, c: 0
593+
};
594+
595+
let array_size = sys::size_of_val(&rng.rsl);
596+
let copy_length = cmp::min(array_size, seed.len());
597+
598+
// manually create a &mut [u8] slice of randrsl to copy into.
599+
let dest = unsafe { cast::transmute((&mut rng.rsl, array_size)) };
600+
vec::bytes::copy_memory(dest, seed, copy_length);
601+
rng.init(true);
602+
rng
603+
}
604+
605+
/// Create an ISAAC random number generator using the default
606+
/// fixed seed.
607+
fn new_unseeded() -> IsaacRng {
608+
let mut rng = IsaacRng {
609+
cnt: 0,
610+
rsl: [0, .. RAND_SIZE],
611+
mem: [0, .. RAND_SIZE],
612+
a: 0, b: 0, c: 0
613+
};
614+
rng.init(false);
615+
rng
616+
}
617+
618+
/// Initialises `self`. If `use_rsl` is true, then use the current value
619+
/// of `rsl` as a seed, otherwise construct one algorithmically (not
620+
/// randomly).
621+
priv fn init(&self, use_rsl: bool) {
622+
macro_rules! init_mut_many (
623+
($( $var:ident ),* = $val:expr ) => {
624+
let mut $( $var = $val ),*;
625+
}
626+
);
627+
init_mut_many!(a, b, c, d, e, f, g, h = 0x9e3779b9);
628+
629+
630+
macro_rules! mix(
631+
() => {{
632+
a^=b<<11; d+=a; b+=c;
633+
b^=c>>2; e+=b; c+=d;
634+
c^=d<<8; f+=c; d+=e;
635+
d^=e>>16; g+=d; e+=f;
636+
e^=f<<10; h+=e; f+=g;
637+
f^=g>>4; a+=f; g+=h;
638+
g^=h<<8; b+=g; h+=a;
639+
h^=a>>9; c+=h; a+=b;
640+
}}
641+
);
642+
643+
for 4.times { mix!(); }
644+
645+
if use_rsl {
646+
macro_rules! memloop (
647+
($arr:expr) => {{
648+
for u32::range_step(0, RAND_SIZE, 8) |i| {
649+
a+=$arr[i ]; b+=$arr[i+1];
650+
c+=$arr[i+2]; d+=$arr[i+3];
651+
e+=$arr[i+4]; f+=$arr[i+5];
652+
g+=$arr[i+6]; h+=$arr[i+7];
653+
mix!();
654+
self.mem[i ]=a; self.mem[i+1]=b;
655+
self.mem[i+2]=c; self.mem[i+3]=d;
656+
self.mem[i+4]=e; self.mem[i+5]=f;
657+
self.mem[i+6]=g; self.mem[i+7]=h;
658+
}
659+
}}
660+
);
661+
662+
memloop!(self.rsl);
663+
memloop!(self.mem);
664+
} else {
665+
for u32::range_step(0, RAND_SIZE, 8) |i| {
666+
mix!();
667+
self.mem[i ]=a; self.mem[i+1]=b;
668+
self.mem[i+2]=c; self.mem[i+3]=d;
669+
self.mem[i+4]=e; self.mem[i+5]=f;
670+
self.mem[i+6]=g; self.mem[i+7]=h;
603671
}
604672
}
673+
674+
self.isaac();
605675
}
606-
}
607676

608-
impl Rng for IsaacRng {
609-
pub fn next(&self) -> u32 {
610-
unsafe {
611-
return rustrt::rand_next(self.rng);
677+
/// Refills the output buffer (`self.rsl`)
678+
priv fn isaac(&self) {
679+
self.c += 1;
680+
// abbreviations
681+
let mut a = self.a, b = self.b + self.c;
682+
let mem = &mut self.mem;
683+
let rsl = &mut self.rsl;
684+
685+
static midpoint: uint = RAND_SIZE as uint / 2;
686+
687+
macro_rules! ind (($x:expr) => { mem[($x >> 2) & (RAND_SIZE - 1)] });
688+
macro_rules! rngstep(
689+
($j:expr, $shift:expr) => {{
690+
let base = base + $j;
691+
let mix = if $shift < 0 {
692+
a >> -$shift as uint
693+
} else {
694+
a << $shift as uint
695+
};
696+
697+
let x = mem[base + mr_offset];
698+
a = (a ^ mix) + mem[base + m2_offset];
699+
let y = ind!(x) + a + b;
700+
mem[base + mr_offset] = y;
701+
702+
b = ind!(y >> RAND_SIZE_LEN) + x;
703+
rsl[base + mr_offset] = b;
704+
}}
705+
);
706+
707+
for [(0, midpoint), (midpoint, 0)].each |&(mr_offset, m2_offset)| {
708+
for uint::range_step(0, midpoint, 4) |base| {
709+
rngstep!(0, 13);
710+
rngstep!(1, -6);
711+
rngstep!(2, 2);
712+
rngstep!(3, -16);
713+
}
612714
}
715+
716+
self.a = a;
717+
self.b = b;
718+
self.cnt = RAND_SIZE;
613719
}
614720
}
615721

616-
/// Create a new random seed for IsaacRng::new_seeded
617-
pub fn seed() -> ~[u8] {
618-
unsafe {
619-
let n = rustrt::rand_seed_size() as uint;
620-
let mut s = vec::from_elem(n, 0_u8);
621-
do vec::as_mut_buf(s) |p, sz| {
622-
rustrt::rand_gen_seed(p, sz as size_t)
722+
impl Rng for IsaacRng {
723+
#[inline(always)]
724+
fn next(&self) -> u32 {
725+
if self.cnt == 0 {
726+
// make some more numbers
727+
self.isaac();
623728
}
624-
s
729+
self.cnt -= 1;
730+
self.rsl[self.cnt]
625731
}
626732
}
627733

628-
struct XorShiftRng {
734+
pub struct XorShiftRng {
629735
priv mut x: u32,
630736
priv mut y: u32,
631737
priv mut z: u32,
@@ -660,7 +766,18 @@ pub impl XorShiftRng {
660766
fn new_seeded(x: u32, y: u32, z: u32, w: u32) -> XorShiftRng {
661767
XorShiftRng { x: x, y: y, z: z, w: w }
662768
}
769+
}
663770

771+
/// Create a new random seed.
772+
pub fn seed() -> ~[u8] {
773+
unsafe {
774+
let n = rustrt::rand_seed_size() as uint;
775+
let mut s = vec::from_elem(n, 0_u8);
776+
do vec::as_mut_buf(s) |p, sz| {
777+
rustrt::rand_gen_seed(p, sz as size_t)
778+
}
779+
s
780+
}
664781
}
665782

666783
// used to make space in TLS for a random number generator
@@ -879,6 +996,45 @@ mod tests {
879996
(u8, i8, u16, i16, u32, i32, u64, i64),
880997
(f32, (f64, (float,)))) = random();
881998
}
999+
1000+
#[test]
1001+
fn compare_isaac_implementation() {
1002+
// This is to verify that the implementation of the ISAAC rng is
1003+
// correct (i.e. matches the output of the upstream implementation,
1004+
// which is in the runtime)
1005+
use vec;
1006+
use libc::size_t;
1007+
1008+
#[abi = "cdecl"]
1009+
mod rustrt {
1010+
use libc::size_t;
1011+
1012+
#[allow(non_camel_case_types)] // runtime type
1013+
pub enum rust_rng {}
1014+
1015+
pub extern {
1016+
unsafe fn rand_new_seeded(buf: *u8, sz: size_t) -> *rust_rng;
1017+
unsafe fn rand_next(rng: *rust_rng) -> u32;
1018+
unsafe fn rand_free(rng: *rust_rng);
1019+
}
1020+
}
1021+
1022+
// run against several seeds
1023+
for 10.times {
1024+
unsafe {
1025+
let seed = super::seed();
1026+
let rt_rng = do vec::as_imm_buf(seed) |p, sz| {
1027+
rustrt::rand_new_seeded(p, sz as size_t)
1028+
};
1029+
let rng = IsaacRng::new_seeded(seed);
1030+
1031+
for 10000.times {
1032+
assert_eq!(rng.next(), rustrt::rand_next(rt_rng));
1033+
}
1034+
rustrt::rand_free(rt_rng);
1035+
}
1036+
}
1037+
}
8821038
}
8831039

8841040

0 commit comments

Comments
 (0)