A hash function using only add, sub, ror, and xor
github.com
github.com
It is 48 x86_64 instructions, compared to the 36 instructions of khash in the link. I am sure you could put more data into b, c, and d as parameters, but I've only used it as a U64 to U64 function.
static uint64_t rotl64(uint64_t n, uint8_t k) {
uint64_t a = n << k;
uint64_t b = n >> (64 - k);
return a | b;
}
uint64_t hash(uint64_t a) {
uint64_t b = 0;
uint64_t c = 0;
uint64_t d = 0;
for (uint8_t i = 0; i < 4; i++) {
b ^= rotl64(a + d, 7);
c ^= rotl64(b + a, 9);
d ^= rotl64(c + b, 13);
a ^= rotl64(d + c, 18);
}
return a ^ b ^ c ^ d;
}K-HASH: 26 instructions https://godbolt.org/z/s3f13nGdG
Your-ARX: 48 instructions (even with rorx) https://godbolt.org/z/cv6PeohP6
Even if the dependency chain is too long for the CPU (or you) to split it, the CPU can always run hyperthread while waiting.
As for x86/x86_64 processors compiling with a flag like -march=broadwell will remove quite few movs from the compiled code since the RORX instruction will be available which can take two register parameters, and a constant. Unlike the normal ROR instruction which is just a register and constant or the CL register for the rotation amount.
Now compared to functions that use multiplication. Multiplication can mix bits quite quickly. If the hardware multiplier is fast I don't think it would be faster with than fnv1 considering how simple that function is.
Meaning it's super easy to create collisions? How easy exactly?
It can only take an input of fixed size and return an output of the same size.
"True" hash functions take an input buffer of an arbitrary size, and return an output of a fixed size. The mixer can be a part of the complete hash function, often the key part, but it is not the entire thing.
This hash function builds up a very long dependency chain, so takes about as many clock cycles as lines. It is generally better to keep dependency chains short and independent.
In this case, it would be better to start three or more separate expressions, and xor them together at the end, so they could execute in parallel, maybe 3x as fast.
But if you have an AES instruction, a couple of rounds of that are probably better and faster than what you would invent.
Heres a script to make one from /dev/urandom. Use something.pbm as the file name. size is in octets.
case $# in
1) file="$1"; size="100";;
2) file="$1"; size="$2";;
*) echo >&2 'Usage ... file [size]'; exit 2;;
esac
(echo 'P4
# random.pbm
'
echo $size $size
dd if=/dev/urandom bs=8 count=$((size * size))
) > "$file"In short: it shows why a simple word mixer alone is not enough for a proper hash. It is insecure, fails all tests, and is not as fast as expected. I would not even use it for PRNG's, but I haven't tested that usage yet.
In either case, a hash table is often the most optimal implementation strategy. And unless the keys are guaranteed to be evenly distributed, the identity is an asymptotically bad hash function.
If your keys are maximally clustered then you can use an alternate dense representation. Otherwise, using a hash table with the identity hash function will likely result in many collisions.
- A PRNG takes a pseudo-random state and produces another pseudo-random state
- A mixer takes possibly correlated inputs and must produce outputs as uncorrelated as possible.
I've actually experimented with using xorshift variants as mixers and they fail the basic statistical tests.
It seems to me is that if you want to do that, you would use the value as a seed to set the internal state of the PRNG and then call the PRNG just once to get the hash value.
But in order to do that, you would have to derive the seed from the data in an appropriate way, which is basically the same exercise as defining a general hash function (i.e. projecting a data of arbitrary length into a set of values of fixed length).
So it looks like you are back to square one, more or less.