cachelib/common/FurcHash.cpp (121 lines of code) (raw):

/* * Copyright (c) Facebook, Inc. and its affiliates. * * 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. */ #include <folly/logging/xlog.h> #include <cstdint> #include "cachelib/common/Hash.h" #include "cachelib/common/Utils.h" namespace facebook { namespace cachelib { namespace { // Maximum tries for in-range result before just returning 0. Should be >= // kFurcShift + 9 to maintain a smooth distribution constexpr uint32_t kMaxTries = 32; // Gap in bit index per try; limits us to 2^FURCHASH shards. Making this // larger will sacrifice a modest amount of performance and require a larger // value for kHashCacheSize constexpr uint32_t kFurcShift = 23; // Size of cache for hash values; should be > // (kFurcShift * (kMaxTries * kFurcShift + 1)) / 64 // This value should work up to kFurcShift == 24 : constexpr int32_t kHashCacheSize = 300; struct FurcHashState { uint32_t nParts; int32_t hashIdx; uint64_t hashCache[kHashCacheSize]; }; // MurmurHash2, 64-bit versions, by Austin Appleby // // The same caveats as 32-bit MurmurHash2 apply here - beware of alignment // and endian-ness issues if used across multiple platforms. // // 64-bit hash for 64-bit platforms uint64_t murmurHash64A(const void* key, int len, uint64_t seed) { const uint64_t m = 0xc6a4a7935bd1e995; const int r = 47; uint64_t h = seed ^ (len * m); const uint64_t* data = reinterpret_cast<const uint64_t*>(key); const uint64_t* end = data + (len / 8); while (data != end) { uint64_t k = util::strict_aliasing_safe_read64(data); data++; k *= m; k ^= k >> r; k *= m; h ^= k; h *= m; } const uint8_t* data2 = (const uint8_t*)data; switch (len & 7) { case 7: h ^= (uint64_t)data2[6] << 48; FOLLY_FALLTHROUGH; case 6: h ^= (uint64_t)data2[5] << 40; FOLLY_FALLTHROUGH; case 5: h ^= (uint64_t)data2[4] << 32; FOLLY_FALLTHROUGH; case 4: h ^= (uint64_t)data2[3] << 24; FOLLY_FALLTHROUGH; case 3: h ^= (uint64_t)data2[2] << 16; FOLLY_FALLTHROUGH; case 2: h ^= (uint64_t)data2[1] << 8; FOLLY_FALLTHROUGH; case 1: h ^= (uint64_t)data2[0]; h *= m; }; h ^= h >> r; h *= m; h ^= h >> r; return h; } // MurmurHash64A performance-optimized for hash of uint64_t keys uint64_t murmurRehash64A(uint64_t key) { const uint64_t m = 0xc6a4a7935bd1e995; const int r = 47; uint64_t h = static_cast<uint64_t>(MurmurHash2::kMurmur2Seed) ^ (sizeof(uint64_t) * m); key *= m; key ^= key >> r; key *= m; h ^= key; h *= m; h ^= h >> r; h *= m; h ^= h >> r; return h; } // getbit -- The bitstream generator // Given state and a bit index, provide a pseudorandom bit dependent on both. // Caches hash values. uint32_t getbit(FurcHashState* statep, uint32_t choice) { int32_t newHashIdx = (choice >> 6); // divide by 64 to get 64-bit cache index // See comment above for kHashCacheSize -- this should only fire if the // constants are modified inappropriately. XDCHECK_LT(newHashIdx, kHashCacheSize); if (statep->hashIdx < newHashIdx) { for (int n = statep->hashIdx + 1; n <= newHashIdx; n++) { statep->hashCache[n] = murmurRehash64A(statep->hashCache[n - 1]); } statep->hashIdx = newHashIdx; } // now return selected bit from cache return (statep->hashCache[newHashIdx] >> (choice & 0x3f)) & 0x1; } } // namespace // furcHash -- a consistent hash function using a binary decision tree. // Based on an algorithm by Mark Rabkin with two changes: // 1) Uses murmurHash64A to hash the original key and to generate // additional bits by recursively rehashing // 2) the original recursive algorithm for the decision tree has been // made iterative // // Assumes that "m" is 8 million or less (2^kFurcShift). Making kFurcShift // bigger also makes FurcHash modestly slower. // // Performance is in the sub-500ns range to over 100,000 shards with 13-byte // keys. This version of furcHash is fairly insensitive to key length since // additional bits are generated by re-hashing the initial murmurHash64A. uint32_t furcHash(const void* key, size_t len, uint32_t nPart) { XDCHECK_LE(len, static_cast<size_t>(std::numeric_limits<int>::max())); if (nPart <= 1) { return 0; } FurcHashState state; state.hashCache[0] = murmurHash64A(key, static_cast<int>(len), MurmurHash2::kMurmur2Seed); state.hashIdx = 0; uint32_t bitNum = 0; for (bitNum = 0; nPart > (1ul << bitNum); bitNum++) { } uint32_t choice = bitNum; uint32_t result = 0; for (uint32_t tries = 0; tries < kMaxTries; tries++) { while (!getbit(&state, choice)) { if (--bitNum == 0) { return 0; } choice = bitNum; } choice += kFurcShift; result = 1; for (uint32_t i = 0; i < bitNum - 1; i++) { result = (result << 1) | getbit(&state, choice); choice += kFurcShift; } if (result < nPart) { return result; } } // give up; 0 is legal value in all cases. return 0; } } // namespace cachelib } // namespace facebook