improve the hash "combine" support.

This commit is contained in:
greg
2019-03-31 10:59:35 -04:00
parent e016df0246
commit 80f29e9932
7 changed files with 246 additions and 250 deletions
+1 -1
View File
@@ -49,7 +49,7 @@ before_build:
build_script: build_script:
- cmake --version - cmake --version
- cmake -H. -Bbuild -DPHMAP_BUILD_TESTS=%TEST% -G %CMAKE_GENERATOR% - cmake -H. -Bbuild -DPHMAP_BUILD_TESTS=%TEST% -DPHMAP_BUILD_EXAMPLES=%TEST% -G %CMAKE_GENERATOR%
- cmake --build build --config %CONFIG% --target ALL_BUILD -- /maxcpucount - cmake --build build --config %CONFIG% --target ALL_BUILD -- /maxcpucount
test_script: test_script:
Vendored
+1 -1
View File
@@ -115,4 +115,4 @@ matrix:
dist: trusty dist: trusty
script: script:
mkdir build && cd build && cmake -DPHMAP_BUILD_TESTS=ON -DCMAKE_CXX_COMPILER=${COMPILER} -DCXXFLAGS="${CXX_FLAGS}" .. && cmake --build . && make test mkdir build && cd build && cmake -DPHMAP_BUILD_TESTS=ON -DPHMAP_BUILD_EXAMPLES=ON -DCMAKE_CXX_COMPILER=${COMPILER} -DCXXFLAGS="${CXX_FLAGS}" .. && cmake --build . && make test
Vendored
+37 -62
View File
@@ -159,67 +159,20 @@ The rules are the same as for std::unordered_map, and are valid for all the phma
## Example 2 - providing a hash function for a user-defined class ## Example 2 - providing a hash function for a user-defined class
In order to use a flat_hash_set or flat_hash_map, a hash function should be provided. Even though a the hash function can be provided via the HashFcn template parameter, we recommend injecting a specialization of `std::hash` for the class into the "std" namespace. For example: In order to use a flat_hash_set or flat_hash_map, a hash function should be provided. Even though a the hash function can be provided via the HashFcn template parameter, we recommend injecting a specialization of `std::hash` for the class into the "std" namespace. We provide a convenient and small header `phmap_utils.h` which allows to easily add such specializations.
For example:
** file <Person.h> **
```c++ ```c++
#include <iostream> #include <parallel_hashmap/phmap_utils.h> // minimal header providing phmap::HashState()
#include <functional>
#include <string> #include <string>
#include <parallel_hashmap/phmap.h>
using std::string; using std::string;
struct Person struct Person
{ {
bool operator==(const Person &o) const bool operator==(const Person &o) const
{ return _first == o._first && _last == o._last; }
string _first;
string _last;
};
namespace std
{
// inject specialization of std::hash for Person into namespace std
// ----------------------------------------------------------------
template<>
struct hash<Person>
{
std::size_t operator()(Person const &p) const
{
std::size_t seed = 0;
phmap::hash_combine(seed, p._first);
phmap::hash_combine(seed, p._last);
return seed;
}
};
}
int main()
{
// As we have defined a specialization of std::hash() for Person,
// we can now create flat_hash_set or flat_hash_map of Persons
// ----------------------------------------------------------------
phmap::flat_hash_set<Person> persons = { { "John", "Galt" },
{ "Jane", "Doe" } };
for (auto& p: persons)
std::cout << p._first << ' ' << p._last << '\n';
}
```
The `std::hash` specialization for `Person` combines the hash values for both first and last name using the convenient phmap::hash_combine function, and returns the combined hash value.
phmap::hash_combine is provided by the header `parallel_hashmap/phmap.h`. However, class definitions often appear in header files, and it is desirable to limit the size of headers included in such header files, so we provide the very small header `parallel_hashmap/phmap_utils.h` for that purpose:
```c++
#include <string>
#include <parallel_hashmap/phmap_utils.h>
using std::string;
struct Person
{
bool operator==(const Person &o) const
{ {
return _first == o._first && _last == o._last && _age == o._age; return _first == o._first && _last == o._last && _age == o._age;
} }
@@ -233,21 +186,43 @@ namespace std
{ {
// inject specialization of std::hash for Person into namespace std // inject specialization of std::hash for Person into namespace std
// ---------------------------------------------------------------- // ----------------------------------------------------------------
template<> template<> struct hash<Person>
struct hash<Person>
{ {
std::size_t operator()(Person const &p) const std::size_t operator()(Person const &p) const
{ {
std::size_t seed = 0; return phmap::HashState().combine(0, p._first, p._last, p._age);
phmap::hash_combine(seed, p._first);
phmap::hash_combine(seed, p._last);
phmap::hash_combine(seed, p._age);
return seed;
} }
}; };
} }
``` ```
The `std::hash` specialization for `Person` combines the hash values for both first and last name and age, using the convenient phmap::HashState() function, and returns the combined hash value.
** file <main.cpp> **
```c++
#include "Person.h" // defines Person with std::hash specialization
#include <iostream>
#include <parallel_hashmap/phmap.h>
int main()
{
// As we have defined a specialization of std::hash() for Person,
// we can now create sparse_hash_set or sparse_hash_map of Persons
// ----------------------------------------------------------------
phmap::flat_hash_set<Person> persons =
{ { "John", "Mitchell", 35 },
{ "Jane", "Smith", 32 },
{ "Jane", "Smith", 30 },
};
for (auto& p: persons)
std::cout << p._first << ' ' << p._last << " (" << p._age << ")" << '\n';
}
```
## Thread safety ## Thread safety
+4 -3
View File
@@ -9,11 +9,12 @@ int main()
// we can now create sparse_hash_set or sparse_hash_map of Persons // we can now create sparse_hash_set or sparse_hash_map of Persons
// ---------------------------------------------------------------- // ----------------------------------------------------------------
phmap::flat_hash_set<Person> persons = phmap::flat_hash_set<Person> persons =
{ { "John", "Galt" }, { { "John", "Mitchell", 35 },
{ "Jane", "Doe" } { "Jane", "Smith", 32 },
{ "Jane", "Smith", 30 },
}; };
for (auto& p: persons) for (auto& p: persons)
std::cout << p._first << ' ' << p._last << '\n'; std::cout << p._first << ' ' << p._last << " (" << p._age << ")" << '\n';
} }
+7 -11
View File
@@ -1,8 +1,7 @@
#ifndef phmap_example_hash_std_ #ifndef phmap_example_hash_std_
#define phmap_example_hash_std_ #define phmap_example_hash_std_
#include <parallel_hashmap/phmap_utils.h> #include <parallel_hashmap/phmap_utils.h> // minimal header providing phmap::hash_combine
#include <string> #include <string>
using std::string; using std::string;
@@ -10,26 +9,23 @@ struct Person
{ {
bool operator==(const Person &o) const bool operator==(const Person &o) const
{ {
return _first == o._first && _last == o._last; return _first == o._first && _last == o._last && _age == o._age;
} }
string _first; string _first;
string _last; string _last;
int _age;
}; };
namespace std namespace std
{ {
// inject specialization of std::hash for Person into namespace std // inject specialization of std::hash for Person into namespace std
// ---------------------------------------------------------------- // ----------------------------------------------------------------
template<> template<> struct hash<Person>
struct hash<Person>
{ {
std::size_t operator()(Person const &p) const std::size_t operator()(Person const &p) const
{ {
std::size_t seed = 0; return phmap::HashState().combine(0, p._first, p._last, p._age);
phmap::hash_combine(seed, p._first);
phmap::hash_combine(seed, p._last);
return seed;
} }
}; };
} }
+159 -1
View File
@@ -46,11 +46,11 @@
#include <utility> #include <utility>
#include <mutex> #include <mutex>
#include <array> #include <array>
#include <functional>
#include <cassert> #include <cassert>
#include "phmap_bits.h" #include "phmap_bits.h"
#include "phmap_base.h" #include "phmap_base.h"
#include "phmap_utils.h"
#include "phmap_fwd_decl.h" #include "phmap_fwd_decl.h"
#if PHMAP_HAVE_STD_STRING_VIEW #if PHMAP_HAVE_STD_STRING_VIEW
@@ -58,6 +58,164 @@
#endif #endif
namespace phmap { namespace phmap {
// ---------------------------------------------------------------
// phmap::Hash
// ---------------------------------------------------------------
template <class T>
struct Hash
{
inline size_t operator()(const T& __v) const
{
return std::hash<T>()(__v);
}
};
template <class T>
struct Hash<T *>
{
inline size_t operator()(const T *__v) const noexcept
{
static const size_t shift = 3;
const uintptr_t i = (const uintptr_t)__v;
return static_cast<size_t>(i >> shift);
}
};
// from http://burtleburtle.net/bob/hash/integer.html
// fast and efficient for power of two table sizes where we always
// consider the last bits.
// ---------------------------------------------------------------
inline size_t phmap_mix_32(uint32_t a)
{
a = a ^ (a >> 4);
a = (a ^ 0xdeadbeef) + (a << 5);
a = a ^ (a >> 11);
return static_cast<size_t>(a);
}
// More thorough scrambling as described in
// https://gist.github.com/badboy/6267743
// ----------------------------------------
inline size_t phmap_mix_64(uint64_t a)
{
a = (~a) + (a << 21); // a = (a << 21) - a - 1;
a = a ^ (a >> 24);
a = (a + (a << 3)) + (a << 8); // a * 265
a = a ^ (a >> 14);
a = (a + (a << 2)) + (a << 4); // a * 21
a = a ^ (a >> 28);
a = a + (a << 31);
return static_cast<size_t>(a);
}
template<class ArgumentType, class ResultType>
struct phmap_unary_function
{
typedef ArgumentType argument_type;
typedef ResultType result_type;
};
template <>
struct Hash<bool> : public phmap_unary_function<bool, size_t>
{
inline size_t operator()(bool __v) const noexcept
{ return static_cast<size_t>(__v); }
};
template <>
struct Hash<char> : public phmap_unary_function<char, size_t>
{
inline size_t operator()(char __v) const noexcept
{ return static_cast<size_t>(__v); }
};
template <>
struct Hash<signed char> : public phmap_unary_function<signed char, size_t>
{
inline size_t operator()(signed char __v) const noexcept
{ return static_cast<size_t>(__v); }
};
template <>
struct Hash<unsigned char> : public phmap_unary_function<unsigned char, size_t>
{
inline size_t operator()(unsigned char __v) const noexcept
{ return static_cast<size_t>(__v); }
};
template <>
struct Hash<wchar_t> : public phmap_unary_function<wchar_t, size_t>
{
inline size_t operator()(wchar_t __v) const noexcept
{ return static_cast<size_t>(__v); }
};
template <>
struct Hash<int16_t> : public phmap_unary_function<int16_t, size_t>
{
inline size_t operator()(int16_t __v) const noexcept
{ return phmap_mix_32(static_cast<uint32_t>(__v)); }
};
template <>
struct Hash<uint16_t> : public phmap_unary_function<uint16_t, size_t>
{
inline size_t operator()(uint16_t __v) const noexcept
{ return phmap_mix_32(static_cast<uint32_t>(__v)); }
};
template <>
struct Hash<int32_t> : public phmap_unary_function<int32_t, size_t>
{
inline size_t operator()(int32_t __v) const noexcept
{ return phmap_mix_32(static_cast<uint32_t>(__v)); }
};
template <>
struct Hash<uint32_t> : public phmap_unary_function<uint32_t, size_t>
{
inline size_t operator()(uint32_t __v) const noexcept
{ return phmap_mix_32(static_cast<uint32_t>(__v)); }
};
template <>
struct Hash<int64_t> : public phmap_unary_function<int64_t, size_t>
{
inline size_t operator()(int64_t __v) const noexcept
{ return phmap_mix_64(static_cast<uint64_t>(__v)); }
};
template <>
struct Hash<uint64_t> : public phmap_unary_function<uint64_t, size_t>
{
inline size_t operator()(uint64_t __v) const noexcept
{ return phmap_mix_64(static_cast<uint64_t>(__v)); }
};
template <>
struct Hash<float> : public phmap_unary_function<float, size_t>
{
inline size_t operator()(float __v) const noexcept
{
// -0.0 and 0.0 should return same hash
uint32_t *as_int = reinterpret_cast<uint32_t *>(&__v);
return (__v == 0) ? static_cast<size_t>(0) : phmap_mix_32(*as_int);
}
};
template <>
struct Hash<double> : public phmap_unary_function<double, size_t>
{
inline size_t operator()(double __v) const noexcept
{
// -0.0 and 0.0 should return same hash
uint64_t *as_int = reinterpret_cast<uint64_t *>(&__v);
return (__v == 0) ? static_cast<size_t>(0) : phmap_mix_64(*as_int);
}
};
namespace container_internal { namespace container_internal {
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
+37 -171
View File
@@ -4,6 +4,8 @@
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Copyright (c) 2019, Gregory Popovitch - greg7mdp@gmail.com // Copyright (c) 2019, Gregory Popovitch - greg7mdp@gmail.com
// //
// minimal header providing phmap::hash_combine
//
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
@@ -17,195 +19,59 @@
// limitations under the License. // limitations under the License.
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
//#include <cstddef> #include <cstddef>
#include <functional>
namespace std
{
template<class Key> struct hash;
} // namespace std
namespace phmap namespace phmap
{ {
template <class T> template <class H, int sz> struct Combiner
struct Hash
{ {
inline size_t operator()(const T& __v) const H operator()(H seed, size_t value);
};
template <class H> struct Combiner<H, 4>
{
H operator()(H seed, size_t value)
{ {
return std::hash<T>()(__v); return seed ^ (value + 0x9e3779b9 + (seed << 6) + (seed >> 2));
} }
}; };
template <class T> template <class H> struct Combiner<H, 8>
struct Hash<T *>
{ {
inline size_t operator()(const T *__v) const noexcept H operator()(H seed, size_t value)
{ {
static const size_t shift = 3; return seed ^ (value + size_t(0xc6a4a7935bd1e995) + (seed << 6) + (seed >> 2));
const uintptr_t i = (const uintptr_t)__v;
return static_cast<size_t>(i >> shift);
} }
}; };
// from http://burtleburtle.net/bob/hash/integer.html
// fast and efficient for power of two table sizes where we always // -----------------------------------------------------------------------------
// consider the last bits. template <typename H>
// --------------------------------------------------------------- class HashStateBase {
inline size_t phmap_mix_32(uint32_t a) public:
template <typename T, typename... Ts>
static H combine(H state, const T& value, const Ts&... values);
static H combine(H state) { return state; }
};
template <typename H>
template <typename T, typename... Ts>
H HashStateBase<H>::combine(H seed, const T& v, const Ts&... vs)
{ {
a = a ^ (a >> 4); return HashStateBase<H>::combine(Combiner<H, sizeof(H)>()(
a = (a ^ 0xdeadbeef) + (a << 5); seed, std::hash<T>()(v)), vs...);
a = a ^ (a >> 11);
return static_cast<size_t>(a);
} }
// More thorough scrambling as described in using HashState = HashStateBase<size_t>;
// https://gist.github.com/badboy/6267743
// ----------------------------------------
inline size_t phmap_mix_64(uint64_t a)
{
a = (~a) + (a << 21); // a = (a << 21) - a - 1;
a = a ^ (a >> 24);
a = (a + (a << 3)) + (a << 8); // a * 265
a = a ^ (a >> 14);
a = (a + (a << 2)) + (a << 4); // a * 21
a = a ^ (a >> 28);
a = a + (a << 31);
return static_cast<size_t>(a);
}
template<class ArgumentType, class ResultType> } // namespace phmap
struct phmap_unary_function
{
typedef ArgumentType argument_type;
typedef ResultType result_type;
};
template <>
struct Hash<bool> : public phmap_unary_function<bool, size_t>
{
inline size_t operator()(bool __v) const noexcept
{ return static_cast<size_t>(__v); }
};
template <>
struct Hash<char> : public phmap_unary_function<char, size_t>
{
inline size_t operator()(char __v) const noexcept
{ return static_cast<size_t>(__v); }
};
template <>
struct Hash<signed char> : public phmap_unary_function<signed char, size_t>
{
inline size_t operator()(signed char __v) const noexcept
{ return static_cast<size_t>(__v); }
};
template <>
struct Hash<unsigned char> : public phmap_unary_function<unsigned char, size_t>
{
inline size_t operator()(unsigned char __v) const noexcept
{ return static_cast<size_t>(__v); }
};
template <>
struct Hash<wchar_t> : public phmap_unary_function<wchar_t, size_t>
{
inline size_t operator()(wchar_t __v) const noexcept
{ return static_cast<size_t>(__v); }
};
template <>
struct Hash<int16_t> : public phmap_unary_function<int16_t, size_t>
{
inline size_t operator()(int16_t __v) const noexcept
{ return phmap_mix_32(static_cast<uint32_t>(__v)); }
};
template <>
struct Hash<uint16_t> : public phmap_unary_function<uint16_t, size_t>
{
inline size_t operator()(uint16_t __v) const noexcept
{ return phmap_mix_32(static_cast<uint32_t>(__v)); }
};
template <>
struct Hash<int32_t> : public phmap_unary_function<int32_t, size_t>
{
inline size_t operator()(int32_t __v) const noexcept
{ return phmap_mix_32(static_cast<uint32_t>(__v)); }
};
template <>
struct Hash<uint32_t> : public phmap_unary_function<uint32_t, size_t>
{
inline size_t operator()(uint32_t __v) const noexcept
{ return phmap_mix_32(static_cast<uint32_t>(__v)); }
};
template <>
struct Hash<int64_t> : public phmap_unary_function<int64_t, size_t>
{
inline size_t operator()(int64_t __v) const noexcept
{ return phmap_mix_64(static_cast<uint64_t>(__v)); }
};
template <>
struct Hash<uint64_t> : public phmap_unary_function<uint64_t, size_t>
{
inline size_t operator()(uint64_t __v) const noexcept
{ return phmap_mix_64(static_cast<uint64_t>(__v)); }
};
template <>
struct Hash<float> : public phmap_unary_function<float, size_t>
{
inline size_t operator()(float __v) const noexcept
{
// -0.0 and 0.0 should return same hash
uint32_t *as_int = reinterpret_cast<uint32_t *>(&__v);
return (__v == 0) ? static_cast<size_t>(0) : phmap_mix_32(*as_int);
}
};
template <>
struct Hash<double> : public phmap_unary_function<double, size_t>
{
inline size_t operator()(double __v) const noexcept
{
// -0.0 and 0.0 should return same hash
uint64_t *as_int = reinterpret_cast<uint64_t *>(&__v);
return (__v == 0) ? static_cast<size_t>(0) : phmap_mix_64(*as_int);
}
};
template <class T, int sz> struct Combiner
{
inline void operator()(T& seed, T value);
};
template <class T> struct Combiner<T, 4>
{
inline void operator()(T& seed, T value)
{
seed ^= value + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
};
template <class T> struct Combiner<T, 8>
{
inline void operator()(T& seed, T value)
{
seed ^= value + T(0xc6a4a7935bd1e995) + (seed << 6) + (seed >> 2);
}
};
template <class T>
inline void hash_combine(std::size_t& seed, T const& v)
{
Combiner<std::size_t, sizeof(std::size_t)> combiner;
combiner(seed, phmap::Hash<T>()(v));
}
}
#endif // phmap_utils_h_guard_ #endif // phmap_utils_h_guard_