diff --git a/include/ankerl/unordered_dense.h b/include/ankerl/unordered_dense.h index 362d426f..0b3d2a22 100644 --- a/include/ankerl/unordered_dense.h +++ b/include/ankerl/unordered_dense.h @@ -330,29 +330,19 @@ struct hash::value>::type> { template struct tuple_hash_helper { + // Converts the value into 64bit. If it is an integral type, just cast it. Mixing is doing the rest. + // If it isn't an integral we need to hash it. template - [[nodiscard]] constexpr static auto calc_buf_size() { - if constexpr (std::has_unique_object_representations_v) { - return sizeof(Arg); + [[nodiscard]] constexpr static auto to64(Arg const& arg) -> uint64_t { + if constexpr (std::is_integral_v) { + return static_cast(arg); } else { - return sizeof(hash{}(std::declval())); + return hash{}(arg); } } - // Reads data from back to front. We do this so there's no need for bswap when multiple - // bytes are read (on little endian). This should be a tiny bit faster. - template - [[nodiscard]] constexpr static auto put(std::byte* pos, Arg const& arg) -> std::byte* { - if constexpr (std::has_unique_object_representations_v) { - pos -= sizeof(Arg); - std::memcpy(pos, &arg, sizeof(Arg)); - return pos; - } else { - auto x = hash{}(arg); - pos -= sizeof(x); - std::memcpy(pos, &x, sizeof(x)); - return pos; - } + [[nodiscard]] static auto mix64(uint64_t state, uint64_t v) -> uint64_t { + return detail::wyhash::mix(state + v, uint64_t{0x9ddfea08eb382d69}); } // Creates a buffer that holds all the data from each element of the tuple. If possible we memcpy the data directly. If @@ -360,11 +350,9 @@ struct tuple_hash_helper { // away, so filling the buffer is highly efficient. Finally, call wyhash with this buffer. template [[nodiscard]] static auto calc_hash(T const& t, std::index_sequence) noexcept -> uint64_t { - std::array() + ...)> tmp_buffer; - auto* buf_ptr = tmp_buffer.data() + tmp_buffer.size(); - ((buf_ptr = put(buf_ptr, std::get(t))), ...); - // at this point, buf_ptr==tmp_buffer.data() - return ankerl::unordered_dense::detail::wyhash::hash(tmp_buffer.data(), tmp_buffer.size()); + auto h = uint64_t{}; + ((h = mix64(h, to64(std::get(t)))), ...); + return h; } }; diff --git a/test/app/doctest.h b/test/app/doctest.h index 2979ac8b..446f7163 100644 --- a/test/app/doctest.h +++ b/test/app/doctest.h @@ -22,6 +22,7 @@ namespace doctest { } // namespace doctest #include +#include template +#include // for Rng, doNotOptimizeAway, Bench + +#include +#include + TEST_CASE("tuple_hash") { auto m = ankerl::unordered_dense::map, int>(); auto pair_hash = ankerl::unordered_dense::hash>{}; @@ -24,3 +29,29 @@ TEST_CASE("good_tuple_hash") { REQUIRE(hashes.size() == 256 * 256); } + +TEST_CASE("tuple_hash_with_stringview") { + using T = std::tuple; + + auto t = T(); + std::get<0>(t) = 1; + auto str = std::string("hello"); + std::get<1>(t) = str; + + auto h1 = ankerl::unordered_dense::hash{}(t); + str = "world"; + REQUIRE(std::get<1>(t) == std::string{"world"}); + auto h2 = ankerl::unordered_dense::hash{}(t); + REQUIRE(h1 != h2); +} + +TEST_CASE("bench_tuple_hash" * doctest::test_suite("bench")) { + using T = std::tuple; + + auto h = uint64_t{}; + auto t = std::tuple{}; + ankerl::nanobench::Bench().run("ankerl hash", [&] { + h += ankerl::unordered_dense::hash{}(t); + ++std::get<4>(t); + }); +}