Skip to content

Commit f92a347

Browse files
committed
Fill isaac backwards, and use fill_via_u32_chunks
Also uses a different solution to index without bounds checking, to recover a very little bit of lost performance.
1 parent d7b014c commit f92a347

File tree

1 file changed

+39
-28
lines changed

1 file changed

+39
-28
lines changed

src/prng/isaac.rs

+39-28
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ use core::iter::repeat;
1515
use core::num::Wrapping as w;
1616
use core::fmt;
1717

18+
use rand_core::impls;
19+
1820
use {Rng, SeedFromRng, SeedableRng, Error};
1921

2022
#[allow(non_camel_case_types)]
@@ -87,12 +89,12 @@ const RAND_SIZE: usize = 1 << RAND_SIZE_LEN;
8789
/// [3]: Jean-Philippe Aumasson, [*On the pseudo-random generator ISAAC*]
8890
/// (http://eprint.iacr.org/2006/438)
8991
pub struct IsaacRng {
90-
rsl: [w32; RAND_SIZE],
92+
rsl: [u32; RAND_SIZE],
9193
mem: [w32; RAND_SIZE],
9294
a: w32,
9395
b: w32,
9496
c: w32,
95-
cnt: u32,
97+
index: u32,
9698
}
9799

98100
// Cannot be derived because [u32; 256] does not implement Clone
@@ -105,7 +107,7 @@ impl Clone for IsaacRng {
105107
a: self.a,
106108
b: self.b,
107109
c: self.c,
108-
cnt: self.cnt,
110+
index: self.index,
109111
}
110112
}
111113
}
@@ -149,20 +151,23 @@ impl IsaacRng {
149151
/// - We maintain one index `i` and add `m` or `m2` as base (m2 for the
150152
/// `s[i+128 mod 256]`), relying on the optimizer to turn it into pointer
151153
/// arithmetic.
154+
/// - We fill `rsl` backwards. The reference implementation reads values
155+
/// from `rsl` in reverse. We read them in the normal direction, to make
156+
/// `fill_bytes` a memcopy. To maintain compatibility we fill in reverse.
152157
fn isaac(&mut self) {
153158
self.c += w(1);
154159
// abbreviations
155160
let mut a = self.a;
156161
let mut b = self.b + self.c;
157162
const MIDPOINT: usize = RAND_SIZE / 2;
158163

159-
#[inline(always)]
164+
#[inline]
160165
fn ind(mem:&[w32; RAND_SIZE], v: w32, amount: usize) -> w32 {
161166
let index = (v >> amount).0 as usize % RAND_SIZE;
162167
mem[index]
163168
}
164169

165-
#[inline(always)]
170+
#[inline]
166171
fn rngstep(ctx: &mut IsaacRng,
167172
mix: w32,
168173
a: &mut w32,
@@ -175,7 +180,7 @@ impl IsaacRng {
175180
let y = *a + *b + ind(&ctx.mem, x, 2);
176181
ctx.mem[base + m] = y;
177182
*b = x + ind(&ctx.mem, y, 2 + RAND_SIZE_LEN);
178-
ctx.rsl[base + m] = *b;
183+
ctx.rsl[RAND_SIZE - 1 - base - m] = (*b).0;
179184
}
180185

181186
let mut m = 0;
@@ -198,44 +203,50 @@ impl IsaacRng {
198203

199204
self.a = a;
200205
self.b = b;
201-
self.cnt = RAND_SIZE as u32;
206+
self.index = 0;
202207
}
203208
}
204209

205210
impl Rng for IsaacRng {
206211
#[inline]
207212
fn next_u32(&mut self) -> u32 {
208-
if self.cnt == 0 {
209-
// make some more numbers
213+
// Using a local variable for `index`, and checking the size avoids a
214+
// bounds check later on.
215+
let mut index = self.index as usize;
216+
if index >= RAND_SIZE {
210217
self.isaac();
218+
index = 0;
211219
}
212-
self.cnt -= 1;
213-
214-
// self.cnt is at most RAND_SIZE, but that is before the
215-
// subtraction above. We want to index without bounds
216-
// checking, but this could lead to incorrect code if someone
217-
// misrefactors, so we check, sometimes.
218-
//
219-
// (Changes here should be reflected in Isaac64Rng.next_u64.)
220-
debug_assert!((self.cnt as usize) < RAND_SIZE);
221-
222-
// (the % is cheaply telling the optimiser that we're always
223-
// in bounds, without unsafe. NB. this is a power of two, so
224-
// it optimises to a bitwise mask).
225-
self.rsl[self.cnt as usize % RAND_SIZE].0
220+
221+
let value = self.rsl[index];
222+
self.index += 1;
223+
value
226224
}
227225

226+
#[inline]
228227
fn next_u64(&mut self) -> u64 {
229-
::rand_core::impls::next_u64_via_u32(self)
228+
impls::next_u64_via_u32(self)
230229
}
231230

232231
#[cfg(feature = "i128_support")]
233232
fn next_u128(&mut self) -> u128 {
234-
::rand_core::impls::next_u128_via_u64(self)
233+
impls::next_u128_via_u64(self)
235234
}
236235

237236
fn fill_bytes(&mut self, dest: &mut [u8]) {
238-
::rand_core::impls::fill_bytes_via_u32(self, dest);
237+
let mut read_len = 0;
238+
while read_len < dest.len() {
239+
if self.index as usize >= RAND_SIZE {
240+
self.isaac();
241+
}
242+
243+
let (consumed_u32, filled_u8) =
244+
impls::fill_via_u32_chunks(&mut self.rsl[(self.index as usize)..],
245+
&mut dest[read_len..]);
246+
247+
self.index += consumed_u32 as u32;
248+
read_len += filled_u8;
249+
}
239250
}
240251

241252
fn try_fill(&mut self, dest: &mut [u8]) -> Result<(), Error> {
@@ -300,12 +311,12 @@ fn init(mut mem: [w32; RAND_SIZE], rounds: u32) -> IsaacRng {
300311
}
301312

302313
let mut rng = IsaacRng {
303-
rsl: [w(0); RAND_SIZE],
314+
rsl: [0; RAND_SIZE],
304315
mem: mem,
305316
a: w(0),
306317
b: w(0),
307318
c: w(0),
308-
cnt: 0,
319+
index: 0,
309320
};
310321

311322
// Prepare the first set of results

0 commit comments

Comments
 (0)