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. /// Performs a base-2 Owen scramble on an integer.
pub fn owen2(n: u32, seed: u32) -> u32 { 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; let mut result = n;
for i in 0..32 { for i in 0..32 {
let mask = (!0 << 1) << i; let mask = (!0 << 1) << i; // Two shifts to avoid undefined overflow.
result ^= hash_u32(n & mask, seed) & (1 << i); result ^= hash((n & mask) ^ seed) & (1 << i);
} }
result result
@ -25,6 +29,7 @@ pub fn owen2(n: u32, seed: u32) -> u32 {
/// The implementation below is a full proper base-4 Owen scramble, /// The implementation below is a full proper base-4 Owen scramble,
/// requiring only a 24-byte lookup table. /// requiring only a 24-byte lookup table.
pub fn owen4(n: u32, seed: u32) -> u32 { pub fn owen4(n: u32, seed: u32) -> u32 {
// Bit-packed permutation table.
const PERMUTATION_TABLE: [u8; 24] = [ const PERMUTATION_TABLE: [u8; 24] = [
0 | (1 << 2) | (2 << 4) | (3 << 6), // [0, 1, 2, 3], 0 | (1 << 2) | (2 << 4) | (3 << 6), // [0, 1, 2, 3],
0 | (1 << 2) | (3 << 4) | (2 << 6), // [0, 1, 3, 2], 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], 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; let mut result = 0;
for i in 0..16 { for i in 0..16 {
let mask = (!0 << 2) << (i * 2); let mask = (!0 << 2) << (i * 2); // Two shifts to avoid undefined overflow.
let perm_entry = PERMUTATION_TABLE[(hash_u32(n & mask, seed) % 24) as usize]; 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; let perm_cell_idx = ((n >> (i * 2)) & 0b11) as usize;
result |= (((perm_entry >> (perm_cell_idx * 2)) & 0b11) as u32) << (i * 2); 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 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
}