Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tuple-hash-fix #102

Merged
merged 2 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.12)
project("unordered_dense"
VERSION 4.3.0
VERSION 4.3.1
DESCRIPTION "A fast & densely stored hashmap and hashset based on robin-hood backward shift deletion"
HOMEPAGE_URL "https://github.com/martinus/unordered_dense")

Expand Down
38 changes: 13 additions & 25 deletions include/ankerl/unordered_dense.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
///////////////////////// ankerl::unordered_dense::{map, set} /////////////////////////

// A fast & densely stored hashmap and hashset based on robin-hood backward shift deletion.
// Version 4.3.0
// Version 4.3.1
// https://github.com/martinus/unordered_dense
//
// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
Expand Down Expand Up @@ -32,7 +32,7 @@
// see https://semver.org/spec/v2.0.0.html
#define ANKERL_UNORDERED_DENSE_VERSION_MAJOR 4 // NOLINT(cppcoreguidelines-macro-usage) incompatible API changes
#define ANKERL_UNORDERED_DENSE_VERSION_MINOR 3 // NOLINT(cppcoreguidelines-macro-usage) backwards compatible functionality
#define ANKERL_UNORDERED_DENSE_VERSION_PATCH 0 // NOLINT(cppcoreguidelines-macro-usage) backwards compatible bug fixes
#define ANKERL_UNORDERED_DENSE_VERSION_PATCH 1 // NOLINT(cppcoreguidelines-macro-usage) backwards compatible bug fixes

// API versioning with inline namespace, see https://www.foonathan.net/2018/11/inline-namespaces/

Expand Down Expand Up @@ -330,41 +330,29 @@ struct hash<Enum, typename std::enable_if<std::is_enum<Enum>::value>::type> {

template <typename... Args>
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 <typename Arg>
[[nodiscard]] constexpr static auto calc_buf_size() {
if constexpr (std::has_unique_object_representations_v<Arg>) {
return sizeof(Arg);
[[nodiscard]] constexpr static auto to64(Arg const& arg) -> uint64_t {
if constexpr (std::is_integral_v<Arg>) {
return static_cast<uint64_t>(arg);
} else {
return sizeof(hash<Arg>{}(std::declval<Arg>()));
return hash<Arg>{}(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 <typename Arg>
[[nodiscard]] constexpr static auto put(std::byte* pos, Arg const& arg) -> std::byte* {
if constexpr (std::has_unique_object_representations_v<Arg>) {
pos -= sizeof(Arg);
std::memcpy(pos, &arg, sizeof(Arg));
return pos;
} else {
auto x = hash<Arg>{}(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
// not, we hash the object and use this for the array. Size of the array is known at compile time, and memcpy is optimized
// away, so filling the buffer is highly efficient. Finally, call wyhash with this buffer.
template <typename T, std::size_t... Idx>
[[nodiscard]] static auto calc_hash(T const& t, std::index_sequence<Idx...>) noexcept -> uint64_t {
std::array<std::byte, (calc_buf_size<Args>() + ...)> tmp_buffer;
auto* buf_ptr = tmp_buffer.data() + tmp_buffer.size();
((buf_ptr = put(buf_ptr, std::get<Idx>(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<Idx>(t)))), ...);
return h;
}
};

Expand Down
2 changes: 1 addition & 1 deletion meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
#

project('unordered_dense', 'cpp',
version: '4.3.0',
version: '4.3.1',
license: 'MIT',
default_options : [
'cpp_std=c++17',
Expand Down
1 change: 1 addition & 0 deletions test/app/doctest.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ namespace doctest {
} // namespace doctest

#include <deque>
#include <sstream>

template <class Key,
class T,
Expand Down
1 change: 1 addition & 0 deletions test/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ test_exe = executable(
# disable these two if you don't want them
#dependency('boost'),
#dependency('absl_container', default_options: ['warning_level=0', 'werror=false'])
# dependency('absl_hash', method: 'builtin', default_options: ['warning_level=0', 'werror=false'])
],
)

Expand Down
2 changes: 1 addition & 1 deletion test/unit/namespace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

#include <doctest.h>

namespace versioned_namespace = ankerl::unordered_dense::v4_3_0;
namespace versioned_namespace = ankerl::unordered_dense::v4_3_1;

static_assert(std::is_same_v<versioned_namespace::map<int, int>, ankerl::unordered_dense::map<int, int>>);
static_assert(std::is_same_v<versioned_namespace::hash<int>, ankerl::unordered_dense::hash<int>>);
Expand Down
31 changes: 31 additions & 0 deletions test/unit/tuple_hash.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

#include <app/doctest.h>

#include <third-party/nanobench.h> // for Rng, doNotOptimizeAway, Bench

#include <string>
#include <string_view>

TEST_CASE("tuple_hash") {
auto m = ankerl::unordered_dense::map<std::pair<int, std::string>, int>();
auto pair_hash = ankerl::unordered_dense::hash<std::pair<int, std::string>>{};
Expand All @@ -24,3 +29,29 @@ TEST_CASE("good_tuple_hash") {

REQUIRE(hashes.size() == 256 * 256);
}

TEST_CASE("tuple_hash_with_stringview") {
using T = std::tuple<int, std::string_view>;

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>{}(t);
str = "world";
REQUIRE(std::get<1>(t) == std::string{"world"});
auto h2 = ankerl::unordered_dense::hash<T>{}(t);
REQUIRE(h1 != h2);
}

TEST_CASE("bench_tuple_hash" * doctest::test_suite("bench")) {
using T = std::tuple<char, int, uint16_t, std::byte, uint64_t>;

auto h = uint64_t{};
auto t = std::tuple<char, int, uint16_t, std::byte, uint64_t>{};
ankerl::nanobench::Bench().run("ankerl hash", [&] {
h += ankerl::unordered_dense::hash<T>{}(t);
++std::get<4>(t);
});
}
Loading