SuperFastHash is the quintessential example of how easy it is to go wrong when making your own hash function even if you're incredibly smart. On the surface SFH looks great - It's decently fast, it avalanches fairly well, it's output appears random, and it might even run well on the datasets you try it on, but on the inside it is incredibly flawed.
The mixing loop takes upwards of 16 bytes before the initial bits begin to get mixed well, and it relies very heavily on excessive post-processing to overcome this. Unfortunately post-processing doesn't help if the internal state has already collided due to flawed mixing! The flawed mixing also leads to many bit differentials (more than any other function in the zoo) which increases the chances of similar keys colliding.
Reactions such as "I was shocked to find that there were no significant impediments to this exercise" should always be a warning sign that perhaps it was too easy for a reason!!
finline u32 fastcall SuperFastHash( const u8 *key, u32 len, u32 seed ) {
u32 hash = seed + (u32 )len, tmp;
u32 remainder = ( len & 3 );
len >>= 2;
#define get16bits(d) ( *( (const unsigned short *)( d ) ) )
for ( ; len > 0; len-- ) {
hash += get16bits( key );
tmp = ( ( get16bits( key + 2 ) << 11 ) ^ hash );
hash = ( ( hash << 16 ) ^ tmp );
key += ( 2 * sizeof( unsigned short ) );
hash += ( hash >> 11 );
}
switch ( remainder ) {
case 3: {
hash += ( get16bits ( key ) );
hash ^= ( hash << 16 );
hash ^= ( key[ sizeof( unsigned short ) ] << 18 );
hash += ( hash >> 11 );
} break;
case 2: {
hash += ( get16bits ( key ) );
hash ^= ( hash << 11 );
hash += ( hash >> 17 );
} break;
case 1: {
hash += ( *key );
hash ^= ( hash << 10 );
hash += ( hash >> 1 );
} break;
}
hash ^= ( hash << 3 );
hash += ( hash >> 5 );
hash ^= ( hash << 4 );
hash += ( hash >> 17 );
hash ^= ( hash << 25 );
hash += ( hash >> 6 );
return ( hash );
}