blob: 212669ec0475ff838313d98395348e06a90e115d [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#ifndef IMPALA_UTIL_HASH_UTIL_H
#define IMPALA_UTIL_HASH_UTIL_H
#include "common/logging.h"
#include "common/compiler-util.h"
#include "util/cpu-info.h"
#include "util/sse-util.h"
namespace impala {
/// Utility class to compute hash values.
class HashUtil {
public:
/// Compute the Crc32 hash for data using SSE4 instructions. The input hash parameter is
/// the current hash/seed value.
/// This should only be called if SSE is supported.
/// This is ~4x faster than Fnv/Boost Hash.
/// NOTE: Any changes made to this function need to be reflected in Codegen::GetHashFn.
/// TODO: crc32 hashes with different seeds do not result in different hash functions.
/// The resulting hashes are correlated.
/// TODO: update this to also use SSE4_crc32_u64 and SSE4_crc32_u16 where appropriate.
static uint32_t CrcHash(const void* data, int32_t bytes, uint32_t hash) {
DCHECK(CpuInfo::IsSupported(CpuInfo::SSE4_2));
uint32_t words = bytes / sizeof(uint32_t);
bytes = bytes % sizeof(uint32_t);
const uint32_t* p = reinterpret_cast<const uint32_t*>(data);
while (words--) {
hash = SSE4_crc32_u32(hash, *p);
++p;
}
const uint8_t* s = reinterpret_cast<const uint8_t*>(p);
while (bytes--) {
hash = SSE4_crc32_u8(hash, *s);
++s;
}
// The lower half of the CRC hash has has poor uniformity, so swap the halves
// for anyone who only uses the first several bits of the hash.
hash = (hash << 16) | (hash >> 16);
return hash;
}
/// CrcHash() specialized for 1-byte data
static inline uint32_t CrcHash1(const void* v, uint32_t hash) {
DCHECK(CpuInfo::IsSupported(CpuInfo::SSE4_2));
const uint8_t* s = reinterpret_cast<const uint8_t*>(v);
hash = SSE4_crc32_u8(hash, *s);
hash = (hash << 16) | (hash >> 16);
return hash;
}
/// CrcHash() specialized for 2-byte data
static inline uint32_t CrcHash2(const void* v, uint32_t hash) {
DCHECK(CpuInfo::IsSupported(CpuInfo::SSE4_2));
const uint16_t* s = reinterpret_cast<const uint16_t*>(v);
hash = SSE4_crc32_u16(hash, *s);
hash = (hash << 16) | (hash >> 16);
return hash;
}
/// CrcHash() specialized for 4-byte data
static inline uint32_t CrcHash4(const void* v, uint32_t hash) {
DCHECK(CpuInfo::IsSupported(CpuInfo::SSE4_2));
const uint32_t* p = reinterpret_cast<const uint32_t*>(v);
hash = SSE4_crc32_u32(hash, *p);
hash = (hash << 16) | (hash >> 16);
return hash;
}
/// CrcHash() specialized for 8-byte data
static inline uint32_t CrcHash8(const void* v, uint32_t hash) {
DCHECK(CpuInfo::IsSupported(CpuInfo::SSE4_2));
const uint64_t* p = reinterpret_cast<const uint64_t*>(v);
hash = SSE4_crc32_u64(hash, *p);
hash = (hash << 16) | (hash >> 16);
return hash;
}
/// CrcHash() specialized for 12-byte data
static inline uint32_t CrcHash12(const void* v, uint32_t hash) {
DCHECK(CpuInfo::IsSupported(CpuInfo::SSE4_2));
const uint64_t* p = reinterpret_cast<const uint64_t*>(v);
hash = SSE4_crc32_u64(hash, *p);
++p;
hash = SSE4_crc32_u32(hash, *reinterpret_cast<const uint32_t *>(p));
hash = (hash << 16) | (hash >> 16);
return hash;
}
/// CrcHash() specialized for 16-byte data
static inline uint32_t CrcHash16(const void* v, uint32_t hash) {
DCHECK(CpuInfo::IsSupported(CpuInfo::SSE4_2));
const uint64_t* p = reinterpret_cast<const uint64_t*>(v);
hash = SSE4_crc32_u64(hash, *p);
++p;
hash = SSE4_crc32_u64(hash, *p);
hash = (hash << 16) | (hash >> 16);
return hash;
}
static const uint64_t MURMUR_DEFAULT_SEED = 0x0;
static const uint64_t MURMUR_PRIME = 0xc6a4a7935bd1e995;
static const int MURMUR_R = 47;
/// Murmur2 hash implementation returning 64-bit hashes.
static uint64_t MurmurHash2_64(const void* input, int len, uint64_t seed) {
uint64_t h = seed ^ (len * MURMUR_PRIME);
const uint64_t* data = reinterpret_cast<const uint64_t*>(input);
const uint64_t* end = data + (len / sizeof(uint64_t));
while (data != end) {
uint64_t k = *data++;
k *= MURMUR_PRIME;
k ^= k >> MURMUR_R;
k *= MURMUR_PRIME;
h ^= k;
h *= MURMUR_PRIME;
}
const uint8_t* data2 = reinterpret_cast<const uint8_t*>(data);
switch (len & 7) {
case 7: h ^= uint64_t(data2[6]) << 48;
case 6: h ^= uint64_t(data2[5]) << 40;
case 5: h ^= uint64_t(data2[4]) << 32;
case 4: h ^= uint64_t(data2[3]) << 24;
case 3: h ^= uint64_t(data2[2]) << 16;
case 2: h ^= uint64_t(data2[1]) << 8;
case 1: h ^= uint64_t(data2[0]);
h *= MURMUR_PRIME;
}
h ^= h >> MURMUR_R;
h *= MURMUR_PRIME;
h ^= h >> MURMUR_R;
return h;
}
/// default values recommended by http://isthe.com/chongo/tech/comp/fnv/
static const uint32_t FNV_PRIME = 0x01000193; // 16777619
static const uint32_t FNV_SEED = 0x811C9DC5; // 2166136261
static const uint64_t FNV64_PRIME = 1099511628211UL;
static const uint64_t FNV64_SEED = 14695981039346656037UL;
/// Implementation of the Fowler-Noll-Vo hash function. This is not as performant
/// as boost's hash on int types (2x slower) but has bit entropy.
/// For ints, boost just returns the value of the int which can be pathological.
/// For example, if the data is <1000, 2000, 3000, 4000, ..> and then the mod of 1000
/// is taken on the hash, all values will collide to the same bucket.
/// For string values, Fnv is slightly faster than boost.
/// IMPORTANT: FNV hash suffers from poor diffusion of the least significant bit,
/// which can lead to poor results when input bytes are duplicated.
/// See FnvHash64to32() for how this can be mitigated.
static uint64_t FnvHash64(const void* data, int32_t bytes, uint64_t hash) {
const uint8_t* ptr = reinterpret_cast<const uint8_t*>(data);
while (bytes--) {
hash = (*ptr ^ hash) * FNV64_PRIME;
++ptr;
}
return hash;
}
/// Return a 32-bit hash computed by invoking FNV-64 and folding the result to 32-bits.
/// This technique is recommended instead of FNV-32 since the LSB of an FNV hash is the
/// XOR of the LSBs of its input bytes, leading to poor results for duplicate inputs.
/// The input seed 'hash' is duplicated so the top half of the seed is not all zero.
/// Data length must be at least 1 byte: zero-length data should be handled separately,
/// for example using CombineHash with a unique constant value to avoid returning the
/// hash argument. Zero-length data gives terrible results: the initial hash value is
/// xored with itself cancelling all bits.
static uint32_t FnvHash64to32(const void* data, int32_t bytes, uint32_t hash) {
// IMPALA-2270: this function should never be used for zero-byte inputs.
DCHECK_GT(bytes, 0);
uint64_t hash_u64 = hash | ((uint64_t)hash << 32);
hash_u64 = FnvHash64(data, bytes, hash_u64);
return (hash_u64 >> 32) ^ (hash_u64 & 0xFFFFFFFF);
}
/// Computes the hash value for data. Will call either CrcHash or MurmurHash depending on
/// hardware capabilities.
/// Seed values for different steps of the query execution should use different seeds
/// to prevent accidental key collisions. (See IMPALA-219 for more details).
static uint32_t Hash(const void* data, int32_t bytes, uint32_t seed) {
if (LIKELY(CpuInfo::IsSupported(CpuInfo::SSE4_2))) {
return CrcHash(data, bytes, seed);
} else {
return MurmurHash2_64(data, bytes, seed);
}
}
/// The magic number (used in hash_combine()) 0x9e3779b9 = 2^32 / (golden ratio).
static const uint32_t HASH_COMBINE_SEED = 0x9e3779b9;
/// Combine hashes 'value' and 'seed' to get a new hash value. Similar to
/// boost::hash_combine(), but for uint32_t. This function should be used with a
/// constant first argument to update the hash value for zero-length values such as
/// NULL, boolean, and empty strings.
static inline uint32_t HashCombine32(uint32_t value, uint32_t seed) {
return seed ^ (HASH_COMBINE_SEED + value + (seed << 6) + (seed >> 2));
}
// Get 32 more bits of randomness from a 32-bit hash:
static inline uint32_t Rehash32to32(const uint32_t hash) {
// Constants generated by uuidgen(1) with the -r flag
static const uint64_t m = 0x7850f11ec6d14889ull, a = 0x6773610597ca4c63ull;
// This is strongly universal hashing following Dietzfelbinger's "Universal hashing
// and k-wise independent random variables via integer arithmetic without primes". As
// such, for any two distinct uint32_t's hash1 and hash2, the probability (over the
// randomness of the constants) that any subset of bit positions of
// Rehash32to32(hash1) is equal to the same subset of bit positions
// Rehash32to32(hash2) is minimal.
return (static_cast<uint64_t>(hash) * m + a) >> 32;
}
// The FastHash64 implementation is adapted from
// https://code.google.com/archive/p/fast-hash/
// available under MIT license.
static inline uint64_t FastHashMix(uint64_t h) {
h ^= h >> 23;
h *= 0x2127599bf4325c37ULL;
h ^= h >> 47;
return h;
}
static inline uint64_t FastHash64(const void* buf, int64_t len, uint64_t seed)
{
const uint64_t m = 0x880355f21e6d1965ULL;
const uint64_t* pos = reinterpret_cast<const uint64_t*>(buf);
const uint64_t* end = pos + (len / 8);
uint64_t h = seed ^ (len * m);
uint64_t v;
while (pos != end) {
v = *pos++;
h ^= FastHashMix(v);
h *= m;
}
const uint8_t* pos2 = reinterpret_cast<const uint8_t*>(pos);
v = 0;
switch (len & 7) {
case 7: v ^= static_cast<uint64_t>(pos2[6]) << 48;
case 6: v ^= static_cast<uint64_t>(pos2[5]) << 40;
case 5: v ^= static_cast<uint64_t>(pos2[4]) << 32;
case 4: v ^= static_cast<uint64_t>(pos2[3]) << 24;
case 3: v ^= static_cast<uint64_t>(pos2[2]) << 16;
case 2: v ^= static_cast<uint64_t>(pos2[1]) << 8;
case 1: v ^= static_cast<uint64_t>(pos2[0]);
h ^= FastHashMix(v);
h *= m;
}
return FastHashMix(h);
}
};
}
#endif