// Copyright 2023 PingCAP, Inc. // // Licensed 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. #pragma once #include #include #include #include #include #include #include #include /** Hash functions that are better than the trivial function std::hash. * * Example: when we do aggregation by the visitor ID, the performance increase is more than 5 times. * This is because of following reasons: * - in Yandex, visitor identifier is an integer that has timestamp with seconds resolution in lower bits; * - in typical implementation of standard library, hash function for integers is trivial and just use lower bits; * - traffic is non-uniformly distributed across a day; * - we are using open-addressing linear probing hash tables that are most critical to hash function quality, * and trivial hash function gives disastrous results. */ /** Taken from MurmurHash. This is Murmur finalizer. * Faster than intHash32 when inserting into the hash table UInt64 -> UInt64, where the key is the visitor ID. */ inline DB::UInt64 intHash64(DB::UInt64 x) { x ^= x >> 33; x *= 0xff51afd7ed558ccdULL; x ^= x >> 33; x *= 0xc4ceb9fe1a85ec53ULL; x ^= x >> 33; return x; } /** CRC32C is not very high-quality as a hash function, * according to avalanche and bit independence tests (see SMHasher software), as well as a small number of bits, * but can behave well when used in hash tables, * due to high speed (latency 3 + 1 clock cycle, throughput 1 clock cycle). * Works only with SSE 4.2 support. */ #ifdef __SSE4_2__ #include #endif #if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) #include #include #endif inline DB::UInt64 intHashCRC32(DB::UInt64 x) { #ifdef __SSE4_2__ return _mm_crc32_u64(-1ULL, x); #elif defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) return __crc32cd(-1U, x); #else /// On other platforms we do not have CRC32. NOTE This can be confusing. return intHash64(x); #endif } inline DB::UInt64 intHashCRC32(DB::UInt64 x, DB::UInt64 updated_value) { #ifdef __SSE4_2__ return _mm_crc32_u64(updated_value, x); #elif defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) return __crc32cd(updated_value, x); #else /// On other platforms we do not have CRC32. NOTE This can be confusing. return intHash64(x) ^ updated_value; #endif } template inline DB::UInt64 wideIntHashCRC32(const T & x, DB::UInt64 updated_value) { if constexpr (DB::IsDecimal) { if constexpr (is_fit_register) return intHashCRC32(x.value, updated_value); else return wideIntHashCRC32(x.value, updated_value); } else if constexpr (is_boost_number_v) { auto backend_value = x.backend(); for (size_t i = 0; i < backend_value.size(); ++i) { updated_value = intHashCRC32(backend_value.limbs()[i], updated_value); } return intHashCRC32(backend_value.sign(), updated_value); } else if constexpr (std::is_same_v) { updated_value = intHashCRC32(x.low, updated_value); updated_value = intHashCRC32(x.high, updated_value); return updated_value; } else if constexpr (std::is_same_v) { const auto * begin = reinterpret_cast(&x); updated_value = intHashCRC32(unalignedLoad(begin), updated_value); updated_value = intHashCRC32(unalignedLoad(begin + 1), updated_value); return updated_value; } else if constexpr (std::is_same_v) { updated_value = intHashCRC32(x.a, updated_value); updated_value = intHashCRC32(x.b, updated_value); updated_value = intHashCRC32(x.c, updated_value); updated_value = intHashCRC32(x.d, updated_value); return updated_value; } static_assert( DB::IsDecimal< T> || is_boost_number_v || std::is_same_v || std::is_same_v || std::is_same_v); __builtin_unreachable(); } template inline DB::UInt64 wideIntHashCRC32(const T & x) { return wideIntHashCRC32(x, -1); } inline UInt32 updateWeakHash32(const DB::UInt8 * pos, size_t size, DB::UInt32 updated_value) { if (size < 8) { UInt64 value = 0; switch (size) { case 0: break; case 1: __builtin_memcpy(&value, pos, 1); break; case 2: __builtin_memcpy(&value, pos, 2); break; case 3: __builtin_memcpy(&value, pos, 3); break; case 4: __builtin_memcpy(&value, pos, 4); break; case 5: __builtin_memcpy(&value, pos, 5); break; case 6: __builtin_memcpy(&value, pos, 6); break; case 7: __builtin_memcpy(&value, pos, 7); break; default: __builtin_unreachable(); } reinterpret_cast(&value)[7] = size; return intHashCRC32(value, updated_value); } const auto * end = pos + size; while (pos + 8 <= end) { auto word = unalignedLoad(pos); updated_value = intHashCRC32(word, updated_value); pos += 8; } if (pos < end) { /// If string size is not divisible by 8. /// Lets' assume the string was 'abcdefghXYZ', so it's tail is 'XYZ'. DB::UInt8 tail_size = end - pos; /// Load tailing 8 bytes. Word is 'defghXYZ'. auto word = unalignedLoad(end - 8); /// Prepare mask which will set other 5 bytes to 0. It is 0xFFFFFFFFFFFFFFFF << 5 = 0xFFFFFF0000000000. /// word & mask = '\0\0\0\0\0XYZ' (bytes are reversed because of little ending) word &= (~UInt64(0)) << DB::UInt8(8 * (8 - tail_size)); /// Use least byte to store tail length. word |= tail_size; /// Now word is '\3\0\0\0\0XYZ' updated_value = intHashCRC32(word, updated_value); } return updated_value; } template inline size_t defaultHash64(std::enable_if_t, T> key) { union { T in; DB::UInt64 out; } u; u.out = 0; u.in = key; return intHash64(u.out); } template inline size_t defaultHash64(const std::enable_if_t, T> & key) { if constexpr (std::is_same_v) { return CityHash_v1_0_2::Hash128to64({key.low, key.high}); } else if constexpr (std::is_same_v) { const auto * begin = reinterpret_cast(&key); return CityHash_v1_0_2::Hash128to64({unalignedLoad(begin), unalignedLoad(begin + 1)}); } else if constexpr (std::is_same_v) { return CityHash_v1_0_2::Hash128to64( {CityHash_v1_0_2::Hash128to64({key.a, key.b}), CityHash_v1_0_2::Hash128to64({key.c, key.d})}); } else if constexpr (is_boost_number_v) { return boost::multiprecision::hash_value(key); } static_assert( is_boost_number_v< T> || std::is_same_v || std::is_same_v || std::is_same_v); __builtin_unreachable(); } template struct DefaultHash; template struct DefaultHash && is_fit_register, void>> { std::enable_if_t, size_t> operator()(T key) const { return defaultHash64(key); } }; template struct DefaultHash && !is_fit_register, void>> { size_t operator()(const T & key) const { return defaultHash64(key); } }; template struct DefaultHash>> { size_t operator()(const T & key) const { return defaultHash64(key.value); } }; template <> struct DefaultHash : public StringRefHash { }; template inline size_t hashCRC32(std::enable_if_t, T> key) { union { T in; DB::UInt64 out; } u; u.out = 0; u.in = key; return intHashCRC32(u.out); } template inline size_t hashCRC32(const std::enable_if_t, T> & key) { return wideIntHashCRC32(key); } template struct HashCRC32; #define DEFINE_HASH(T) \ template <> \ struct HashCRC32 \ { \ static_assert(is_fit_register); \ size_t operator()(T key) const { return hashCRC32(key); } \ }; #define DEFINE_HASH_WIDE(T) \ template <> \ struct HashCRC32 \ { \ static_assert(!is_fit_register); \ size_t operator()(const T & key) const { return hashCRC32(key); } \ }; DEFINE_HASH(DB::UInt8) DEFINE_HASH(DB::UInt16) DEFINE_HASH(DB::UInt32) DEFINE_HASH(DB::UInt64) DEFINE_HASH(DB::Int8) DEFINE_HASH(DB::Int16) DEFINE_HASH(DB::Int32) DEFINE_HASH(DB::Int64) DEFINE_HASH(DB::Float32) DEFINE_HASH(DB::Float64) DEFINE_HASH_WIDE(DB::UInt128) DEFINE_HASH_WIDE(DB::UInt256) DEFINE_HASH_WIDE(DB::Int128) DEFINE_HASH_WIDE(DB::Int256) DEFINE_HASH_WIDE(DB::Int512) #undef DEFINE_HASH /// It is reasonable to use for UInt8, UInt16 with sufficient hash table size. struct TrivialHash { template , int> = 0> size_t operator()(T key) const { return key; } size_t operator()(const UInt128 & key) const { return key.low; } size_t operator()(const Int128 & key) const { return key; } size_t operator()(const UInt256 & key) const { return key.a; } template , int> = 0> size_t operator()(const T & key) const { return key; } }; /** A relatively good non-cryptographic hash function from UInt64 to UInt32. * But worse (both in quality and speed) than just cutting intHash64. * Taken from here: http://www.concentric.net/~ttwang/tech/inthash.htm * * Slightly changed compared to the function by link: shifts to the right are accidentally replaced by a cyclic shift to the right. * This change did not affect the smhasher test results. * * It is recommended to use different salt for different tasks. * That was the case that in the database values were sorted by hash (for low-quality pseudo-random spread), * and in another place, in the aggregate function, the same hash was used in the hash table, * as a result, this aggregate function was monstrously slowed due to collisions. * * NOTE Salting is far from perfect, because it commutes with first steps of calculation. * * NOTE As mentioned, this function is slower than intHash64. * But occasionally, it is faster, when written in a loop and loop is vectorized. */ template inline DB::UInt32 intHash32(DB::UInt64 key) { key ^= salt; key = (~key) + (key << 18); key = key ^ ((key >> 31) | (key << 33)); key = key * 21; key = key ^ ((key >> 11) | (key << 53)); key = key + (key << 6); key = key ^ ((key >> 22) | (key << 42)); return key; } /// For containers. template struct IntHash32; template struct IntHash32, void>> { size_t operator()(T key) const { return intHash32(key); } }; template struct IntHash32, void>> { size_t operator()(const T & key) const { if constexpr (std::is_same_v) { return intHash32(key.low ^ key.high); } else if constexpr (std::is_same_v) { return intHash32(key.a ^ key.b ^ key.c ^ key.d); } else { return intHash32(defaultHash64(key)); } } };