Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit b647f4c

Browse files
committedDec 19, 2023
tuple hash: simplify
Hashing is now similar to abseil, which is essentially doing the same.
1 parent 6f94f87 commit b647f4c

File tree

3 files changed

+43
-23
lines changed

3 files changed

+43
-23
lines changed
 

‎include/ankerl/unordered_dense.h

+11-23
Original file line numberDiff line numberDiff line change
@@ -330,41 +330,29 @@ struct hash<Enum, typename std::enable_if<std::is_enum<Enum>::value>::type> {
330330

331331
template <typename... Args>
332332
struct tuple_hash_helper {
333+
// Converts the value into 64bit. If it is an integral type, just cast it. Mixing is doing the rest.
334+
// If it isn't an integral we need to hash it.
333335
template <typename Arg>
334-
[[nodiscard]] constexpr static auto calc_buf_size() {
335-
if constexpr (std::has_unique_object_representations_v<Arg>) {
336-
return sizeof(Arg);
336+
[[nodiscard]] constexpr static auto to64(Arg const& arg) -> uint64_t {
337+
if constexpr (std::is_integral_v<Arg>) {
338+
return static_cast<uint64_t>(arg);
337339
} else {
338-
return sizeof(hash<Arg>{}(std::declval<Arg>()));
340+
return hash<Arg>{}(arg);
339341
}
340342
}
341343

342-
// Reads data from back to front. We do this so there's no need for bswap when multiple
343-
// bytes are read (on little endian). This should be a tiny bit faster.
344-
template <typename Arg>
345-
[[nodiscard]] constexpr static auto put(std::byte* pos, Arg const& arg) -> std::byte* {
346-
if constexpr (std::has_unique_object_representations_v<Arg>) {
347-
pos -= sizeof(Arg);
348-
std::memcpy(pos, &arg, sizeof(Arg));
349-
return pos;
350-
} else {
351-
auto x = hash<Arg>{}(arg);
352-
pos -= sizeof(x);
353-
std::memcpy(pos, &x, sizeof(x));
354-
return pos;
355-
}
344+
[[nodiscard]] static auto mix64(uint64_t state, uint64_t v) -> uint64_t {
345+
return detail::wyhash::mix(state + v, uint64_t{0x9ddfea08eb382d69});
356346
}
357347

358348
// Creates a buffer that holds all the data from each element of the tuple. If possible we memcpy the data directly. If
359349
// not, we hash the object and use this for the array. Size of the array is known at compile time, and memcpy is optimized
360350
// away, so filling the buffer is highly efficient. Finally, call wyhash with this buffer.
361351
template <typename T, std::size_t... Idx>
362352
[[nodiscard]] static auto calc_hash(T const& t, std::index_sequence<Idx...>) noexcept -> uint64_t {
363-
std::array<std::byte, (calc_buf_size<Args>() + ...)> tmp_buffer;
364-
auto* buf_ptr = tmp_buffer.data() + tmp_buffer.size();
365-
((buf_ptr = put(buf_ptr, std::get<Idx>(t))), ...);
366-
// at this point, buf_ptr==tmp_buffer.data()
367-
return ankerl::unordered_dense::detail::wyhash::hash(tmp_buffer.data(), tmp_buffer.size());
353+
auto h = uint64_t{};
354+
((h = mix64(h, to64(std::get<Idx>(t)))), ...);
355+
return h;
368356
}
369357
};
370358

‎test/meson.build

+1
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ test_exe = executable(
175175
# disable these two if you don't want them
176176
#dependency('boost'),
177177
#dependency('absl_container', default_options: ['warning_level=0', 'werror=false'])
178+
# dependency('absl_hash', method: 'builtin', default_options: ['warning_level=0', 'werror=false'])
178179
],
179180
)
180181

‎test/unit/tuple_hash.cpp

+31
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
#include <app/doctest.h>
44

5+
#include <third-party/nanobench.h> // for Rng, doNotOptimizeAway, Bench
6+
7+
#include <string>
8+
#include <string_view>
9+
510
TEST_CASE("tuple_hash") {
611
auto m = ankerl::unordered_dense::map<std::pair<int, std::string>, int>();
712
auto pair_hash = ankerl::unordered_dense::hash<std::pair<int, std::string>>{};
@@ -24,3 +29,29 @@ TEST_CASE("good_tuple_hash") {
2429

2530
REQUIRE(hashes.size() == 256 * 256);
2631
}
32+
33+
TEST_CASE("tuple_hash_with_stringview") {
34+
using T = std::tuple<int, std::string_view>;
35+
36+
auto t = T();
37+
std::get<0>(t) = 1;
38+
auto str = std::string("hello");
39+
std::get<1>(t) = str;
40+
41+
auto h1 = ankerl::unordered_dense::hash<T>{}(t);
42+
str = "world";
43+
REQUIRE(std::get<1>(t) == "world");
44+
auto h2 = ankerl::unordered_dense::hash<T>{}(t);
45+
REQUIRE(h1 != h2);
46+
}
47+
48+
TEST_CASE("bench_tuple_hash" * doctest::test_suite("bench")) {
49+
using T = std::tuple<char, int, uint16_t, std::byte, uint64_t>;
50+
51+
auto h = uint64_t{};
52+
auto t = std::tuple<char, int, uint16_t, std::byte, uint64_t>{};
53+
ankerl::nanobench::Bench().run("ankerl hash", [&] {
54+
h += ankerl::unordered_dense::hash<T>{}(t);
55+
++std::get<4>(t);
56+
});
57+
}

0 commit comments

Comments
 (0)
Please sign in to comment.