diff --git a/.appveyor.yml b/.appveyor.yml index c0c7ad3..4694304 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -49,7 +49,7 @@ before_build: build_script: - 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 test_script: diff --git a/.travis.yml b/.travis.yml index 7fc3273..b773dd1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -115,4 +115,4 @@ matrix: dist: trusty 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 diff --git a/README.md b/README.md index c67db03..4c7dba9 100644 --- a/README.md +++ b/README.md @@ -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 -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 ** ```c++ -#include -#include +#include // minimal header providing phmap::HashState() #include -#include - using std::string; -struct Person +struct Person { - 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 - { - 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 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 -#include - -using std::string; - -struct Person -{ - bool operator==(const Person &o) const + bool operator==(const Person &o) const { 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 // ---------------------------------------------------------------- - template<> - struct hash + template<> struct hash { 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); - phmap::hash_combine(seed, p._age); - return seed; + return phmap::HashState().combine(0, p._first, p._last, p._age); } }; } ``` +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 ** + +```c++ +#include "Person.h" // defines Person with std::hash specialization + +#include +#include + +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 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 diff --git a/examples/hash_std.cc b/examples/hash_std.cc index 20eef8f..04551ff 100644 --- a/examples/hash_std.cc +++ b/examples/hash_std.cc @@ -9,11 +9,12 @@ int main() // we can now create sparse_hash_set or sparse_hash_map of Persons // ---------------------------------------------------------------- phmap::flat_hash_set persons = - { { "John", "Galt" }, - { "Jane", "Doe" } + { { "John", "Mitchell", 35 }, + { "Jane", "Smith", 32 }, + { "Jane", "Smith", 30 }, }; for (auto& p: persons) - std::cout << p._first << ' ' << p._last << '\n'; + std::cout << p._first << ' ' << p._last << " (" << p._age << ")" << '\n'; } diff --git a/examples/hash_std.h b/examples/hash_std.h index 03dfe64..660f5b4 100644 --- a/examples/hash_std.h +++ b/examples/hash_std.h @@ -1,8 +1,7 @@ #ifndef phmap_example_hash_std_ #define phmap_example_hash_std_ -#include - +#include // minimal header providing phmap::hash_combine #include using std::string; @@ -10,26 +9,23 @@ struct Person { 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 _last; + int _age; }; namespace std { -// inject specialization of std::hash for Person into namespace std -// ---------------------------------------------------------------- - template<> - struct hash + // inject specialization of std::hash for Person into namespace std + // ---------------------------------------------------------------- + template<> struct hash { 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; + return phmap::HashState().combine(0, p._first, p._last, p._age); } }; } diff --git a/parallel_hashmap/phmap.h b/parallel_hashmap/phmap.h index 2d175f2..b1b3245 100644 --- a/parallel_hashmap/phmap.h +++ b/parallel_hashmap/phmap.h @@ -46,11 +46,11 @@ #include #include #include +#include #include #include "phmap_bits.h" #include "phmap_base.h" -#include "phmap_utils.h" #include "phmap_fwd_decl.h" #if PHMAP_HAVE_STD_STRING_VIEW @@ -58,6 +58,164 @@ #endif namespace phmap { + +// --------------------------------------------------------------- +// phmap::Hash +// --------------------------------------------------------------- +template +struct Hash +{ + inline size_t operator()(const T& __v) const + { + return std::hash()(__v); + } +}; + +template +struct Hash +{ + 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(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(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(a); +} + +template +struct phmap_unary_function +{ + typedef ArgumentType argument_type; + typedef ResultType result_type; +}; + +template <> +struct Hash : public phmap_unary_function +{ + inline size_t operator()(bool __v) const noexcept + { return static_cast(__v); } +}; + +template <> +struct Hash : public phmap_unary_function +{ + inline size_t operator()(char __v) const noexcept + { return static_cast(__v); } +}; + +template <> +struct Hash : public phmap_unary_function +{ + inline size_t operator()(signed char __v) const noexcept + { return static_cast(__v); } +}; + +template <> +struct Hash : public phmap_unary_function +{ + inline size_t operator()(unsigned char __v) const noexcept + { return static_cast(__v); } +}; + +template <> +struct Hash : public phmap_unary_function +{ + inline size_t operator()(wchar_t __v) const noexcept + { return static_cast(__v); } +}; + +template <> +struct Hash : public phmap_unary_function +{ + inline size_t operator()(int16_t __v) const noexcept + { return phmap_mix_32(static_cast(__v)); } +}; + +template <> +struct Hash : public phmap_unary_function +{ + inline size_t operator()(uint16_t __v) const noexcept + { return phmap_mix_32(static_cast(__v)); } +}; + +template <> +struct Hash : public phmap_unary_function +{ + inline size_t operator()(int32_t __v) const noexcept + { return phmap_mix_32(static_cast(__v)); } +}; + +template <> +struct Hash : public phmap_unary_function +{ + inline size_t operator()(uint32_t __v) const noexcept + { return phmap_mix_32(static_cast(__v)); } +}; + +template <> +struct Hash : public phmap_unary_function +{ + inline size_t operator()(int64_t __v) const noexcept + { return phmap_mix_64(static_cast(__v)); } +}; + +template <> +struct Hash : public phmap_unary_function +{ + inline size_t operator()(uint64_t __v) const noexcept + { return phmap_mix_64(static_cast(__v)); } +}; + +template <> +struct Hash : public phmap_unary_function +{ + inline size_t operator()(float __v) const noexcept + { + // -0.0 and 0.0 should return same hash + uint32_t *as_int = reinterpret_cast(&__v); + return (__v == 0) ? static_cast(0) : phmap_mix_32(*as_int); + } +}; + +template <> +struct Hash : public phmap_unary_function +{ + inline size_t operator()(double __v) const noexcept + { + // -0.0 and 0.0 should return same hash + uint64_t *as_int = reinterpret_cast(&__v); + return (__v == 0) ? static_cast(0) : phmap_mix_64(*as_int); + } +}; + + namespace container_internal { // -------------------------------------------------------------------------- diff --git a/parallel_hashmap/phmap_utils.h b/parallel_hashmap/phmap_utils.h index 1db808a..dbc6c5a 100644 --- a/parallel_hashmap/phmap_utils.h +++ b/parallel_hashmap/phmap_utils.h @@ -4,6 +4,8 @@ // --------------------------------------------------------------------------- // Copyright (c) 2019, Gregory Popovitch - greg7mdp@gmail.com // +// minimal header providing phmap::hash_combine +// // 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 @@ -17,195 +19,59 @@ // limitations under the License. // --------------------------------------------------------------------------- -//#include -#include +#include + +namespace std +{ + template struct hash; +} // namespace std namespace phmap { -template -struct Hash +template struct Combiner { - inline size_t operator()(const T& __v) const + H operator()(H seed, size_t value); +}; + +template struct Combiner +{ + H operator()(H seed, size_t value) { - return std::hash()(__v); + return seed ^ (value + 0x9e3779b9 + (seed << 6) + (seed >> 2)); } }; -template -struct Hash +template struct Combiner { - inline size_t operator()(const T *__v) const noexcept + H operator()(H seed, size_t value) { - static const size_t shift = 3; - const uintptr_t i = (const uintptr_t)__v; - return static_cast(i >> shift); + return seed ^ (value + size_t(0xc6a4a7935bd1e995) + (seed << 6) + (seed >> 2)); } }; -// 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) + +// ----------------------------------------------------------------------------- +template +class HashStateBase { +public: + template + static H combine(H state, const T& value, const Ts&... values); + + static H combine(H state) { return state; } +}; + +template +template +H HashStateBase::combine(H seed, const T& v, const Ts&... vs) { - a = a ^ (a >> 4); - a = (a ^ 0xdeadbeef) + (a << 5); - a = a ^ (a >> 11); - return static_cast(a); + return HashStateBase::combine(Combiner()( + seed, std::hash()(v)), vs...); } -// 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(a); -} +using HashState = HashStateBase; -template -struct phmap_unary_function -{ - typedef ArgumentType argument_type; - typedef ResultType result_type; -}; - -template <> -struct Hash : public phmap_unary_function -{ - inline size_t operator()(bool __v) const noexcept - { return static_cast(__v); } -}; - -template <> -struct Hash : public phmap_unary_function -{ - inline size_t operator()(char __v) const noexcept - { return static_cast(__v); } -}; - -template <> -struct Hash : public phmap_unary_function -{ - inline size_t operator()(signed char __v) const noexcept - { return static_cast(__v); } -}; - -template <> -struct Hash : public phmap_unary_function -{ - inline size_t operator()(unsigned char __v) const noexcept - { return static_cast(__v); } -}; - -template <> -struct Hash : public phmap_unary_function -{ - inline size_t operator()(wchar_t __v) const noexcept - { return static_cast(__v); } -}; - -template <> -struct Hash : public phmap_unary_function -{ - inline size_t operator()(int16_t __v) const noexcept - { return phmap_mix_32(static_cast(__v)); } -}; - -template <> -struct Hash : public phmap_unary_function -{ - inline size_t operator()(uint16_t __v) const noexcept - { return phmap_mix_32(static_cast(__v)); } -}; - -template <> -struct Hash : public phmap_unary_function -{ - inline size_t operator()(int32_t __v) const noexcept - { return phmap_mix_32(static_cast(__v)); } -}; - -template <> -struct Hash : public phmap_unary_function -{ - inline size_t operator()(uint32_t __v) const noexcept - { return phmap_mix_32(static_cast(__v)); } -}; - -template <> -struct Hash : public phmap_unary_function -{ - inline size_t operator()(int64_t __v) const noexcept - { return phmap_mix_64(static_cast(__v)); } -}; - -template <> -struct Hash : public phmap_unary_function -{ - inline size_t operator()(uint64_t __v) const noexcept - { return phmap_mix_64(static_cast(__v)); } -}; - -template <> -struct Hash : public phmap_unary_function -{ - inline size_t operator()(float __v) const noexcept - { - // -0.0 and 0.0 should return same hash - uint32_t *as_int = reinterpret_cast(&__v); - return (__v == 0) ? static_cast(0) : phmap_mix_32(*as_int); - } -}; - -template <> -struct Hash : public phmap_unary_function -{ - inline size_t operator()(double __v) const noexcept - { - // -0.0 and 0.0 should return same hash - uint64_t *as_int = reinterpret_cast(&__v); - return (__v == 0) ? static_cast(0) : phmap_mix_64(*as_int); - } -}; - -template struct Combiner -{ - inline void operator()(T& seed, T value); -}; - -template struct Combiner -{ - inline void operator()(T& seed, T value) - { - seed ^= value + 0x9e3779b9 + (seed << 6) + (seed >> 2); - } -}; - -template struct Combiner -{ - inline void operator()(T& seed, T value) - { - seed ^= value + T(0xc6a4a7935bd1e995) + (seed << 6) + (seed >> 2); - } -}; - -template -inline void hash_combine(std::size_t& seed, T const& v) -{ - Combiner combiner; - - combiner(seed, phmap::Hash()(v)); -} - -} +} // namespace phmap #endif // phmap_utils_h_guard_