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.
This commit is contained in:
Nathan Vegdahl 2022-07-23 13:24:24 -07:00
parent 40d643b334
commit f95e869848

View File

@ -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
}