From f95e869848e0b2a66cb0facc75062778c9881cb9 Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Sat, 23 Jul 2022 13:24:24 -0700 Subject: [PATCH] Give Owen scramble functions their own hash. This lets us move the seeding overhead outside the main loop, which in turn lets us avoid taking it every round. --- src/scramble.rs | 44 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/src/scramble.rs b/src/scramble.rs index 881a96f..78df767 100644 --- a/src/scramble.rs +++ b/src/scramble.rs @@ -1,12 +1,16 @@ -use crate::hash::hash_u32; - /// Performs a base-2 Owen scramble on an integer. pub fn owen2(n: u32, seed: u32) -> u32 { + // Multiply by a large random prime and xor by a random number. + // This is to ensure that the seed doesn't behave poorly with + // e.g. incrementing parameters, and also that zero doesn't + // map to zero in the hash function. + let seed = seed.wrapping_mul(0xe8559dcb) ^ 0x372fcdb9; + let mut result = n; for i in 0..32 { - let mask = (!0 << 1) << i; - result ^= hash_u32(n & mask, seed) & (1 << i); + let mask = (!0 << 1) << i; // Two shifts to avoid undefined overflow. + result ^= hash((n & mask) ^ seed) & (1 << i); } result @@ -25,6 +29,7 @@ pub fn owen2(n: u32, seed: u32) -> u32 { /// The implementation below is a full proper base-4 Owen scramble, /// requiring only a 24-byte lookup table. pub fn owen4(n: u32, seed: u32) -> u32 { + // Bit-packed permutation table. const PERMUTATION_TABLE: [u8; 24] = [ 0 | (1 << 2) | (2 << 4) | (3 << 6), // [0, 1, 2, 3], 0 | (1 << 2) | (3 << 4) | (2 << 6), // [0, 1, 3, 2], @@ -52,11 +57,23 @@ pub fn owen4(n: u32, seed: u32) -> u32 { 3 | (2 << 2) | (1 << 4) | (0 << 6), // [3, 2, 1, 0], ]; + // Multiply by a large random prime and xor by a random number. + // This is to ensure that the seed doesn't behave poorly with + // e.g. incrementing parameters, and also that zero doesn't + // map to zero in the hash function. + let seed = seed.wrapping_mul(0xe8559dcb) ^ 0x372fcdb9; + let mut result = 0; for i in 0..16 { - let mask = (!0 << 2) << (i * 2); - let perm_entry = PERMUTATION_TABLE[(hash_u32(n & mask, seed) % 24) as usize]; + let mask = (!0 << 2) << (i * 2); // Two shifts to avoid undefined overflow. + let perm_entry = PERMUTATION_TABLE[ + // The xor with `i` is to ensure runs of zeros in `n` still + // result in different shuffles on each iteration. `i` is + // shifted to avoid interacting poorly with an incrementing + // `n`. + (hash((n & mask) ^ seed ^ (i << 16)) % 24) as usize + ]; let perm_cell_idx = ((n >> (i * 2)) & 0b11) as usize; result |= (((perm_entry >> (perm_cell_idx * 2)) & 0b11) as u32) << (i * 2); @@ -64,3 +81,18 @@ pub fn owen4(n: u32, seed: u32) -> u32 { result } + +//------------------------------------------------------------- + +/// Fast bit-mixing hash for use in the functions above. +#[inline(always)] +pub fn hash(mut n: u32) -> u32 { + // From https://github.com/skeeto/hash-prospector + n ^= n >> 16; + n = n.wrapping_mul(0x21f0aaad); + n ^= n >> 15; + n = n.wrapping_mul(0xd35a2d97); + n ^= n >> 15; + + n +}