I discovered this blog post from Daniel Lemire about a hash function called wyhash. The original wyhash function and associated wyrand function are available in this wyhash repo. The relevant code is in wyhash.h. Below are snippets from wyhash.h related to the wyrand function:
// 128bit multiply function
static inline uint64_t _wyrot(uint64_t x) { return (x>>32)|(x<<32); }
static inline void _wymum(uint64_t *A, uint64_t *B){
#if(WYHASH_32BIT_MUM)
uint64_t hh=(*A>>32)*(*B>>32), hl=(*A>>32)*(uint32_t)*B, lh=(uint32_t)*A*(*B>>32), ll=(uint64_t)(uint32_t)*A*(uint32_t)*B;
#if(WYHASH_CONDOM>1)
*A^=_wyrot(hl)^hh; *B^=_wyrot(lh)^ll;
#else
*A=_wyrot(hl)^hh; *B=_wyrot(lh)^ll;
#endif
#elif defined(__SIZEOF_INT128__)
__uint128_t r=*A; r*=*B;
#if(WYHASH_CONDOM>1)
*A^=(uint64_t)r; *B^=(uint64_t)(r>>64);
#else
*A=(uint64_t)r; *B=(uint64_t)(r>>64);
#endif
#elif defined(_MSC_VER) && defined(_M_X64)
#if(WYHASH_CONDOM>1)
uint64_t a, b;
a=_umul128(*A,*B,&b);
*A^=a; *B^=b;
#else
*A=_umul128(*A,*B,B);
#endif
#else
uint64_t ha=*A>>32, hb=*B>>32, la=(uint32_t)*A, lb=(uint32_t)*B, hi, lo;
uint64_t rh=ha*hb, rm0=ha*lb, rm1=hb*la, rl=la*lb, t=rl+(rm0<<32), c=t<rl;
lo=t+(rm1<<32); c+=lo<t; hi=rh+(rm0>>32)+(rm1>>32)+c;
#if(WYHASH_CONDOM>1)
*A^=lo; *B^=hi;
#else
*A=lo; *B=hi;
#endif
#endif
}
// Multiply and xor mix function, aka MUM
static inline uint64_t _wymix(uint64_t A, uint64_t B) {
_wymum(&A, &B);
return A^B;
}
//The wyrand PRNG that pass BigCrush and PractRand
static inline uint64_t wyrand(uint64_t *seed) {
*seed+=0x2d358dccaa6c78a5ull;
return _wymix(*seed, *seed^0x8bb84b93962eacc9ull);
}
The blog post links to a Swift implementation of wyrand that is available at the SwiftWyhash repo. The code in the SwiftWyhash repo lags behind recent changes in the wyhash repo by several years. So I attempted to update the Swift implementation based on the latest code in the wyhash repo. Below is my Swift attempt:
import Foundation
struct WyRand: RandomNumberGenerator {
private var state : UInt64
init(seed: UInt64 = mach_absolute_time()) {
state = seed
}
mutating func next() -> UInt64 {
state &+= 0x2d358dccaa6c78a5
let mul = state.multipliedFullWidth(by: state ^ 0x8bb84b93962eacc9)
return mul.low ^ mul.high
}
}
Using this random number generator to fill an array with random values is shown below. This is significantly faster than using Swift's default Double.random
.
let n = 100_000_000
var rng = WyRand()
let result = Array<Double>(unsafeUninitializedCapacity: n) { buffer, initCount in
for i in 0..<n {
buffer[i] = Double.random(in: 0..<1, using: &rng)
}
initCount = n
}
print(result[0])
I'm not great with C/C++ code so can someone take a look at the wyrand function in wyhash.h and let me know if the WyRand
struct is a good Swift representation of the wyrand function?