Commit b61b327d authored by Xiao Shi's avatar Xiao Shi Committed by Facebook Github Bot

mark numerical hasher(s) noexcept

Summary:
The libstdc++ implementation of `std::unordered_map|set` determines whether to
cache a per-node hash code based on (1) whether the supplied hasher is fast
(true by default) and (2) whether the hashing operation will throw.

Mark the `operator()` of hashers of numeric types (integral, float, enum)
`noexcept`, such that stl unordered containers won't cache a hash code. This is
essentially free memomy savings, as comparing the cached hash code is as
expensive as comparing the numerical types directly.

Reviewed By: luciang

Differential Revision: D7784214

fbshipit-source-id: 020e1c38265f33a482b6ba913a11f1a383e840f8
parent c4350342
......@@ -45,7 +45,9 @@ namespace hash {
// This is the Hash128to64 function from Google's cityhash (available
// under the MIT License). We use it to reduce multiple 64 bit hashes
// into a single hash.
inline uint64_t hash_128_to_64(const uint64_t upper, const uint64_t lower) {
inline uint64_t hash_128_to_64(
const uint64_t upper,
const uint64_t lower) noexcept {
// Murmur-inspired hashing.
const uint64_t kMul = 0x9ddfea08eb382d69ULL;
uint64_t a = (lower ^ upper) * kMul;
......@@ -112,7 +114,7 @@ size_t hash_combine(const T& t, const Ts&... ts) {
* Thomas Wang 64 bit mix hash function
*/
inline uint64_t twang_mix64(uint64_t key) {
inline uint64_t twang_mix64(uint64_t key) noexcept {
key = (~key) + (key << 21); // key *= (1 << 21) - 1; key -= 1;
key = key ^ (key >> 24);
key = key + (key << 3) + (key << 8); // key *= 1 + (1 << 3) + (1 << 8)
......@@ -129,7 +131,7 @@ inline uint64_t twang_mix64(uint64_t key) {
* Note that twang_unmix64 is significantly slower than twang_mix64.
*/
inline uint64_t twang_unmix64(uint64_t key) {
inline uint64_t twang_unmix64(uint64_t key) noexcept {
// See the comments in jenkins_rev_unmix32 for an explanation as to how this
// was generated
key *= 4611686016279904257U;
......@@ -160,7 +162,7 @@ inline uint32_t twang_32from64(uint64_t key) {
* Robert Jenkins' reversible 32 bit mix hash function
*/
inline uint32_t jenkins_rev_mix32(uint32_t key) {
inline uint32_t jenkins_rev_mix32(uint32_t key) noexcept {
key += (key << 12); // key *= (1 + (1 << 12))
key ^= (key >> 22);
key += (key << 4); // key *= (1 + (1 << 4))
......@@ -370,7 +372,7 @@ namespace detail {
template <typename I>
struct integral_hasher {
size_t operator()(I const& i) const {
size_t operator()(I const& i) const noexcept {
static_assert(sizeof(I) <= 16, "Input type is too wide");
/* constexpr */ if (sizeof(I) <= 4) {
auto const i32 = static_cast<int32_t>(i); // impl accident: sign-extends
......@@ -394,7 +396,7 @@ using integral_hasher_avalanches =
template <typename F>
struct float_hasher {
size_t operator()(F const& f) const {
size_t operator()(F const& f) const noexcept {
static_assert(sizeof(F) <= 8, "Input type is too wide");
if (f == F{}) { // Ensure 0 and -0 get the same hash.
......@@ -414,7 +416,7 @@ struct hasher;
struct Hash {
template <class T>
size_t operator()(const T& v) const {
size_t operator()(const T& v) const noexcept(noexcept(hasher<T>()(v))) {
return hasher<T>()(v);
}
......@@ -464,7 +466,7 @@ struct IsAvalanchingHasher<Hash, K> : IsAvalanchingHasher<hasher<K>, K> {};
template <>
struct hasher<bool> {
size_t operator()(bool key) const {
size_t operator()(bool key) const noexcept {
// Make sure that all the output bits depend on the input.
return key ? std::numeric_limits<size_t>::max() : 0;
}
......@@ -533,7 +535,8 @@ struct IsAvalanchingHasher<hasher<std::string>, K> : std::true_type {};
template <typename T>
struct hasher<T, typename std::enable_if<std::is_enum<T>::value, void>::type> {
size_t operator()(T key) const {
// Hash for the underlying_type (integral types) are marked noexcept above.
size_t operator()(T key) const noexcept {
return Hash()(static_cast<typename std::underlying_type<T>::type>(key));
}
};
......
......@@ -223,6 +223,151 @@ TEST(Hash, integral_types) {
EXPECT_EQ(setSize, hashes.size());
}
namespace {
enum class TestEnum {
MIN = 0,
ITEM = 1,
MAX = 2,
};
enum class TestBigEnum : uint64_t {
ITEM = 1,
};
struct TestStruct {};
} // namespace
namespace std {
template <>
struct hash<TestEnum> {
std::size_t operator()(TestEnum const& e) const noexcept {
return hash<int>()(static_cast<int>(e));
}
};
template <>
struct hash<TestStruct> {
std::size_t operator()(TestStruct const&) const noexcept {
return 0;
}
};
} // namespace std
namespace {
thread_local size_t allocatedMemorySize{0};
template <class T>
class TestAlloc {
public:
using Alloc = std::allocator<T>;
using value_type = typename Alloc::value_type;
using pointer = typename Alloc::pointer;
using const_pointer = typename Alloc::const_pointer;
using reference = typename Alloc::reference;
using const_reference = typename Alloc::const_reference;
using size_type = typename Alloc::size_type;
using propagate_on_container_swap = std::true_type;
using propagate_on_container_copy_assignment = std::true_type;
using propagate_on_container_move_assignment = std::true_type;
TestAlloc() {}
template <class T2>
TestAlloc(TestAlloc<T2> const& other) noexcept : a_(other.a_) {}
template <class T2>
TestAlloc& operator=(TestAlloc<T2> const& other) noexcept {
a_ = other.a_;
return *this;
}
template <class T2>
TestAlloc(TestAlloc<T2>&& other) noexcept : a_(std::move(other.a_)) {}
template <class T2>
TestAlloc& operator=(TestAlloc<T2>&& other) noexcept {
a_ = std::move(other.a_);
return *this;
}
static size_t getAllocatedMemorySize() {
return allocatedMemorySize;
}
static void resetTracking() {
allocatedMemorySize = 0;
}
T* allocate(size_t n) {
allocatedMemorySize += n * sizeof(T);
return a_.allocate(n);
}
void deallocate(T* p, size_t n) {
allocatedMemorySize -= n * sizeof(T);
a_.deallocate(p, n);
}
private:
std::allocator<T> a_;
template <class U>
friend class TestAlloc;
};
template <class T1, class T2>
bool operator==(TestAlloc<T1> const&, TestAlloc<T2> const&) {
return true;
}
template <class T1, class T2>
bool operator!=(TestAlloc<T1> const&, TestAlloc<T2> const&) {
return false;
}
template <class M, class A>
std::vector<size_t> getStats(size_t iter) {
std::vector<size_t> ret;
ret.reserve(iter);
A::resetTracking();
M m;
ret.push_back(A::getAllocatedMemorySize());
for (size_t i = 1; i < iter; ++i) {
m.insert(std::make_pair(
folly::to<typename M::key_type>(i), typename M::mapped_type{}));
ret.push_back(A::getAllocatedMemorySize());
}
return ret;
}
template <typename K, typename V, typename H>
void testNoCachedHashCode() {
using A = TestAlloc<std::pair<const K, V>>;
using M = std::unordered_map<K, V, std::hash<K>, std::equal_to<K>, A>;
using MActual = std::unordered_map<K, V, H, std::equal_to<K>, A>;
constexpr int kIter = 10;
auto expected = getStats<M, A>(kIter);
auto actual = getStats<MActual, A>(kIter);
ASSERT_EQ(expected.size(), actual.size());
for (size_t i = 0; i < expected.size(); ++i) {
ASSERT_EQ(expected[i], actual[i]);
}
}
} // namespace
TEST(Hash, noCachedHashCode) {
testNoCachedHashCode<bool, char, folly::hasher<bool>>();
testNoCachedHashCode<int, char, folly::hasher<int>>();
testNoCachedHashCode<double, char, folly::hasher<double>>();
testNoCachedHashCode<TestEnum, char, folly::hasher<TestEnum>>();
testNoCachedHashCode<bool, std::string, folly::Hash>();
testNoCachedHashCode<int, std::string, folly::Hash>();
testNoCachedHashCode<double, std::string, folly::Hash>();
testNoCachedHashCode<TestEnum, std::string, folly::Hash>();
}
TEST(Hash, integer_conversion) {
folly::hasher<uint64_t> h;
uint64_t k = 10;
......@@ -481,32 +626,6 @@ INSTANTIATE_TEST_CASE_P(
FNVTestParam{"http://norvig.com/21-days.html", // 136
0x07aaa640476e0b9a}));
namespace {
enum class TestEnum {
MIN = 0,
ITEM = 1,
MAX = 2,
};
enum class TestBigEnum : uint64_t {
ITEM = 1,
};
struct TestStruct {};
} // namespace
namespace std {
template <>
struct hash<TestEnum> : hash<int> {};
template <>
struct hash<TestStruct> {
std::size_t operator()(TestStruct const&) const {
return 0;
}
};
} // namespace std
//////// static checks
static_assert(!folly::IsAvalanchingHasher<std::hash<int>, int>::value, "");
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment