Merge commit 'f1087e81ecdca5a59ba5ffca684c955c5b38f7c2' as 'third_party/unordered_dense'
This commit is contained in:
+75
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include <app/counter.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
namespace checksum {
|
||||
|
||||
// final step from MurmurHash3
|
||||
[[nodiscard]] static inline auto mix(uint64_t k) -> uint64_t {
|
||||
k ^= k >> 33U;
|
||||
k *= 0xff51afd7ed558ccdULL;
|
||||
k ^= k >> 33U;
|
||||
k *= 0xc4ceb9fe1a85ec53ULL;
|
||||
k ^= k >> 33U;
|
||||
return k;
|
||||
}
|
||||
|
||||
[[maybe_unused]] [[nodiscard]] static inline auto mix(std::string_view data) -> uint64_t {
|
||||
static constexpr uint64_t fnv_offset_basis = UINT64_C(14695981039346656037);
|
||||
static constexpr uint64_t fnv_prime = UINT64_C(1099511628211);
|
||||
|
||||
uint64_t val = fnv_offset_basis;
|
||||
for (auto c : data) {
|
||||
val ^= static_cast<uint64_t>(c);
|
||||
val *= fnv_prime;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
[[maybe_unused]] [[nodiscard]] static inline auto mix(counter::obj const& cdv) -> uint64_t {
|
||||
return mix(cdv.get());
|
||||
}
|
||||
|
||||
// from boost::hash_combine, with additional fmix64 of value
|
||||
[[maybe_unused]] [[nodiscard]] static inline auto combine(uint64_t seed, uint64_t value) -> uint64_t {
|
||||
return seed ^ (value + 0x9e3779b9 + (seed << 6U) + (seed >> 2U));
|
||||
}
|
||||
|
||||
// calculates a hash of any iterable map. Order is irrelevant for the hash's result, as it simply
|
||||
// xors the elements together.
|
||||
template <typename M>
|
||||
[[nodiscard]] auto map(const M& map) -> uint64_t {
|
||||
uint64_t combined_hash = 1;
|
||||
|
||||
uint64_t num_elements = 0;
|
||||
for (auto const& entry : map) {
|
||||
auto entry_hash = combine(mix(entry.first), mix(entry.second));
|
||||
|
||||
combined_hash ^= entry_hash;
|
||||
++num_elements;
|
||||
}
|
||||
|
||||
return combine(combined_hash, num_elements);
|
||||
}
|
||||
|
||||
// map of maps
|
||||
template <typename MM>
|
||||
[[nodiscard]] auto mapmap(const MM& mapmap) -> uint64_t {
|
||||
uint64_t combined_hash = 1;
|
||||
|
||||
uint64_t num_elements = 0;
|
||||
for (auto const& entry : mapmap) {
|
||||
auto entry_hash = combine(mix(entry.first), map(entry.second));
|
||||
|
||||
combined_hash ^= entry_hash;
|
||||
++num_elements;
|
||||
}
|
||||
|
||||
return combine(combined_hash, num_elements);
|
||||
}
|
||||
|
||||
} // namespace checksum
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
#include <app/counter.h>
|
||||
|
||||
#include <app/print.h> // for print
|
||||
|
||||
#include <cstdlib> // for abort
|
||||
#include <ostream> // for ostream
|
||||
#include <stdexcept> // for runtime_error
|
||||
#include <unordered_set> // for unordered_set
|
||||
#include <utility> // for swap, pair
|
||||
|
||||
static inline constexpr bool counter_enable_unordered_set = true;
|
||||
|
||||
auto singleton_constructed_objects() -> std::unordered_set<counter::obj const*>& {
|
||||
static std::unordered_set<counter::obj const*> static_data{};
|
||||
return static_data;
|
||||
}
|
||||
|
||||
counter::obj::obj()
|
||||
: m_data(0)
|
||||
, m_counts(nullptr) {
|
||||
if constexpr (counter_enable_unordered_set) {
|
||||
if (!singleton_constructed_objects().emplace(this).second) {
|
||||
test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
++static_default_ctor;
|
||||
}
|
||||
|
||||
counter::obj::obj(const size_t& data, counter& counts)
|
||||
: m_data(data)
|
||||
, m_counts(&counts) {
|
||||
if constexpr (counter_enable_unordered_set) {
|
||||
if (!singleton_constructed_objects().emplace(this).second) {
|
||||
test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
++m_counts->m_data.m_ctor;
|
||||
}
|
||||
|
||||
counter::obj::obj(const counter::obj& o)
|
||||
: m_data(o.m_data)
|
||||
, m_counts(o.m_counts) {
|
||||
if constexpr (counter_enable_unordered_set) {
|
||||
if (1 != singleton_constructed_objects().count(&o)) {
|
||||
test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__);
|
||||
std::abort();
|
||||
}
|
||||
if (!singleton_constructed_objects().emplace(this).second) {
|
||||
test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
if (nullptr != m_counts) {
|
||||
++m_counts->m_data.m_copy_ctor;
|
||||
}
|
||||
}
|
||||
|
||||
counter::obj::obj(counter::obj&& o) noexcept
|
||||
: m_data(o.m_data)
|
||||
, m_counts(o.m_counts) {
|
||||
if constexpr (counter_enable_unordered_set) {
|
||||
if (1 != singleton_constructed_objects().count(&o)) {
|
||||
test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__);
|
||||
std::abort();
|
||||
}
|
||||
if (!singleton_constructed_objects().emplace(this).second) {
|
||||
test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
if (nullptr != m_counts) {
|
||||
++m_counts->m_data.m_move_ctor;
|
||||
}
|
||||
}
|
||||
|
||||
counter::obj::~obj() {
|
||||
if constexpr (counter_enable_unordered_set) {
|
||||
if (1 != singleton_constructed_objects().erase(this)) {
|
||||
test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
if (nullptr != m_counts) {
|
||||
++m_counts->m_data.m_dtor;
|
||||
} else {
|
||||
++static_dtor;
|
||||
}
|
||||
}
|
||||
|
||||
auto counter::obj::operator==(obj const& o) const -> bool {
|
||||
if constexpr (counter_enable_unordered_set) {
|
||||
if (1 != singleton_constructed_objects().count(this) || 1 != singleton_constructed_objects().count(&o)) {
|
||||
test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
if (nullptr != m_counts) {
|
||||
++m_counts->m_data.m_equals;
|
||||
}
|
||||
return m_data == o.m_data;
|
||||
}
|
||||
|
||||
auto counter::obj::operator<(obj const& o) const -> bool {
|
||||
if constexpr (counter_enable_unordered_set) {
|
||||
if (1 != singleton_constructed_objects().count(this) || 1 != singleton_constructed_objects().count(&o)) {
|
||||
test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
if (nullptr != m_counts) {
|
||||
++m_counts->m_data.m_less;
|
||||
}
|
||||
return m_data < o.m_data;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp)
|
||||
auto counter::obj::operator=(obj const& o) -> counter::obj& {
|
||||
if constexpr (counter_enable_unordered_set) {
|
||||
if (1 != singleton_constructed_objects().count(this) || 1 != singleton_constructed_objects().count(&o)) {
|
||||
test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
m_counts = o.m_counts;
|
||||
if (nullptr != m_counts) {
|
||||
++m_counts->m_data.m_assign;
|
||||
}
|
||||
m_data = o.m_data;
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto counter::obj::operator=(obj&& o) noexcept -> counter::obj& {
|
||||
if constexpr (counter_enable_unordered_set) {
|
||||
if (1 != singleton_constructed_objects().count(this) || 1 != singleton_constructed_objects().count(&o)) {
|
||||
test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
if (nullptr != o.m_counts) {
|
||||
m_counts = o.m_counts;
|
||||
}
|
||||
m_data = o.m_data;
|
||||
if (nullptr != m_counts) {
|
||||
++m_counts->m_data.m_move_assign;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto counter::obj::get() const -> size_t const& {
|
||||
if (nullptr != m_counts) {
|
||||
++m_counts->m_data.m_const_get;
|
||||
}
|
||||
return m_data;
|
||||
}
|
||||
|
||||
auto counter::obj::get() -> size_t& {
|
||||
if (nullptr != m_counts) {
|
||||
++m_counts->m_data.m_get;
|
||||
}
|
||||
return m_data;
|
||||
}
|
||||
|
||||
void counter::obj::swap(obj& other) {
|
||||
if constexpr (counter_enable_unordered_set) {
|
||||
if (1 != singleton_constructed_objects().count(this) || 1 != singleton_constructed_objects().count(&other)) {
|
||||
test::print("ERROR at {}({}): {}\n", __FILE__, __LINE__, __func__);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
using std::swap;
|
||||
swap(m_data, other.m_data);
|
||||
swap(m_counts, other.m_counts);
|
||||
if (nullptr != m_counts) {
|
||||
++m_counts->m_data.m_swaps;
|
||||
}
|
||||
}
|
||||
|
||||
auto counter::obj::get_for_hash() const -> size_t {
|
||||
if (nullptr != m_counts) {
|
||||
++m_counts->m_data.m_hash;
|
||||
}
|
||||
return m_data;
|
||||
}
|
||||
|
||||
counter::counter() {
|
||||
counter::static_default_ctor = 0;
|
||||
counter::static_dtor = 0;
|
||||
}
|
||||
|
||||
void counter::check_all_done() const {
|
||||
if constexpr (counter_enable_unordered_set) {
|
||||
// check that all are destructed
|
||||
if (!singleton_constructed_objects().empty()) {
|
||||
test::print("ERROR at ~counter(): got {} objects still alive!", singleton_constructed_objects().size());
|
||||
std::abort();
|
||||
}
|
||||
|
||||
if (m_data.m_dtor + static_dtor !=
|
||||
m_data.m_ctor + static_default_ctor + m_data.m_copy_ctor + m_data.m_default_ctor + m_data.m_move_ctor) {
|
||||
test::print("ERROR at ~counter(): number of counts does not match!\n");
|
||||
test::print(
|
||||
"{} dtor + {} staticDtor != {} ctor + {} staticDefaultCtor + {} copyCtor + {} defaultCtor + {} moveCtor\n",
|
||||
m_data.m_dtor,
|
||||
static_dtor,
|
||||
m_data.m_ctor,
|
||||
static_default_ctor,
|
||||
m_data.m_copy_ctor,
|
||||
m_data.m_default_ctor,
|
||||
m_data.m_move_ctor);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
counter::~counter() {
|
||||
check_all_done();
|
||||
}
|
||||
|
||||
auto counter::total() const -> size_t {
|
||||
return m_data.m_ctor + static_default_ctor + m_data.m_copy_ctor + (m_data.m_dtor + static_dtor) + m_data.m_equals +
|
||||
m_data.m_less + m_data.m_assign + m_data.m_swaps + m_data.m_get + m_data.m_const_get + m_data.m_hash +
|
||||
m_data.m_move_ctor + m_data.m_move_assign;
|
||||
}
|
||||
|
||||
void counter::operator()(std::string_view title) {
|
||||
m_records += fmt::format("{:9}{:9}{:9}{:9}{:9}{:9}{:9}{:9}{:9}{:9}{:9}{:9}{:9}|{:9}| {}\n",
|
||||
m_data.m_ctor,
|
||||
static_default_ctor,
|
||||
m_data.m_copy_ctor,
|
||||
m_data.m_dtor + static_dtor,
|
||||
m_data.m_assign,
|
||||
m_data.m_swaps,
|
||||
m_data.m_get,
|
||||
m_data.m_const_get,
|
||||
m_data.m_hash,
|
||||
m_data.m_equals,
|
||||
m_data.m_less,
|
||||
m_data.m_move_ctor,
|
||||
m_data.m_move_assign,
|
||||
total(),
|
||||
title);
|
||||
}
|
||||
|
||||
auto operator<<(std::ostream& os, counter const& c) -> std::ostream& {
|
||||
return os << c.m_records;
|
||||
}
|
||||
|
||||
auto operator new(size_t /*unused*/, counter::obj* /*unused*/) -> void* {
|
||||
throw std::runtime_error("operator new overload is taken! Cast to void* to ensure the void pointer overload is taken.");
|
||||
}
|
||||
size_t counter::static_default_ctor = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
size_t counter::static_dtor = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
#pragma once
|
||||
|
||||
#include <fmt/core.h> // for format_context, format_parse_context, format_to
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <functional> // for hash
|
||||
#include <iosfwd> // for ostream
|
||||
#include <string> // for allocator, string
|
||||
#include <string_view> // for hash, string_view
|
||||
#include <type_traits>
|
||||
|
||||
class counter {
|
||||
public:
|
||||
struct data_t {
|
||||
size_t m_ctor{};
|
||||
size_t m_default_ctor{};
|
||||
size_t m_copy_ctor{};
|
||||
size_t m_dtor{};
|
||||
size_t m_assign{};
|
||||
size_t m_swaps{};
|
||||
size_t m_get{};
|
||||
size_t m_const_get{};
|
||||
size_t m_hash{};
|
||||
size_t m_equals{};
|
||||
size_t m_less{};
|
||||
size_t m_move_ctor{};
|
||||
size_t m_move_assign{};
|
||||
|
||||
friend auto operator==(data_t const& a, data_t const& b) -> bool {
|
||||
static_assert(std::has_unique_object_representations_v<data_t>);
|
||||
return 0 == std::memcmp(&a, &b, sizeof(data_t));
|
||||
}
|
||||
|
||||
friend auto operator!=(data_t const& a, data_t const& b) -> bool {
|
||||
return !(a == b);
|
||||
}
|
||||
};
|
||||
|
||||
counter(counter const&) = delete;
|
||||
counter(counter&&) = delete;
|
||||
auto operator=(counter const&) -> counter& = delete;
|
||||
auto operator=(counter&&) -> counter&& = delete;
|
||||
|
||||
// Obj for only swaps & equals. Used for optimizing.
|
||||
// Can't use static counters here because I want to do it in parallel.
|
||||
class obj {
|
||||
public:
|
||||
// required for operator[]
|
||||
obj();
|
||||
obj(const size_t& data, counter& counts);
|
||||
obj(const obj& o);
|
||||
obj(obj&& o) noexcept;
|
||||
~obj();
|
||||
|
||||
auto operator==(const obj& o) const -> bool;
|
||||
auto operator<(const obj& o) const -> bool;
|
||||
auto operator=(const obj& o) -> obj&;
|
||||
auto operator=(obj&& o) noexcept -> obj&;
|
||||
|
||||
[[nodiscard]] auto get() const -> size_t const&;
|
||||
auto get() -> size_t&;
|
||||
|
||||
void swap(obj& other);
|
||||
[[nodiscard]] auto get_for_hash() const -> size_t;
|
||||
|
||||
private:
|
||||
size_t m_data;
|
||||
counter* m_counts;
|
||||
};
|
||||
|
||||
counter();
|
||||
~counter();
|
||||
|
||||
void check_all_done() const;
|
||||
|
||||
[[nodiscard]] auto ctor() const -> size_t {
|
||||
return m_data.m_ctor;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto default_ctor() const -> size_t {
|
||||
return m_data.m_default_ctor;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto copy_ctor() const -> size_t {
|
||||
return m_data.m_copy_ctor;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto dtor() const -> size_t {
|
||||
return m_data.m_dtor;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto equals() const -> size_t {
|
||||
return m_data.m_equals;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto less() const -> size_t {
|
||||
return m_data.m_less;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto assign() const -> size_t {
|
||||
return m_data.m_assign;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto swaps() const -> size_t {
|
||||
return m_data.m_swaps;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get() const -> size_t {
|
||||
return m_data.m_get;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto const_get() const -> size_t {
|
||||
return m_data.m_const_get;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto hash() const -> size_t {
|
||||
return m_data.m_hash;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto move_ctor() const -> size_t {
|
||||
return m_data.m_move_ctor;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto move_assign() const -> size_t {
|
||||
return m_data.m_move_assign;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto data() const -> data_t const& {
|
||||
return m_data;
|
||||
}
|
||||
|
||||
friend auto operator<<(std::ostream& os, counter const& c) -> std::ostream&;
|
||||
|
||||
void operator()(std::string_view title);
|
||||
|
||||
[[nodiscard]] auto total() const -> size_t;
|
||||
|
||||
static size_t static_default_ctor; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
static size_t static_dtor; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
private:
|
||||
data_t m_data{};
|
||||
|
||||
std::string m_records =
|
||||
"\n ctor defctor cpyctor dtor assign swaps get cnstget hash equals less ctormv assignmv| total |\n";
|
||||
};
|
||||
|
||||
// Throws an exception, this overload should never be taken!
|
||||
inline auto operator new(size_t s, counter::obj* ptr) -> void*;
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<counter::obj> {
|
||||
[[nodiscard]] auto operator()(const counter::obj& c) const noexcept -> size_t {
|
||||
return hash<size_t>{}(c.get_for_hash());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<counter::obj> {
|
||||
static constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.end();
|
||||
}
|
||||
static auto format(counter::obj const& o, fmt::format_context& ctx) -> decltype(ctx.out()) {
|
||||
return fmt::format_to(ctx.out(), "{}", o.get());
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,138 @@
|
||||
#pragma once
|
||||
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
// Source: https://github.com/bitcoin/bitcoin/blob/master/src/memusage.h#L41-L61
|
||||
static inline auto malloc_usage(size_t alloc) -> size_t {
|
||||
static_assert(sizeof(void*) == 8 || sizeof(void*) == 4);
|
||||
|
||||
// Measured on libc6 2.19 on Linux.
|
||||
if constexpr (sizeof(void*) == 8U) {
|
||||
return ((alloc + 31U) >> 4U) << 4U;
|
||||
} else {
|
||||
return ((alloc + 15U) >> 3U) << 3U;
|
||||
}
|
||||
}
|
||||
|
||||
class counts_for_allocator {
|
||||
struct measurement_internal {
|
||||
std::chrono::steady_clock::time_point m_tp{};
|
||||
size_t m_diff{};
|
||||
};
|
||||
|
||||
struct measurement {
|
||||
std::chrono::steady_clock::duration m_duration{};
|
||||
size_t m_num_bytes_allocated{};
|
||||
};
|
||||
|
||||
std::vector<measurement_internal> m_measurements{};
|
||||
std::chrono::steady_clock::time_point m_start = std::chrono::steady_clock::now();
|
||||
|
||||
template <typename Op>
|
||||
void each_measurement(Op op) const {
|
||||
auto total_bytes = size_t();
|
||||
auto const start_time = m_start;
|
||||
for (auto const& m : m_measurements) {
|
||||
bool is_add = true;
|
||||
size_t bytes = m.m_diff;
|
||||
if (bytes > (0U - bytes)) {
|
||||
// negative number
|
||||
is_add = false;
|
||||
bytes = 0U - bytes;
|
||||
}
|
||||
|
||||
if (is_add) {
|
||||
total_bytes += malloc_usage(bytes);
|
||||
} else {
|
||||
total_bytes -= malloc_usage(bytes);
|
||||
}
|
||||
op(measurement{m.m_tp - start_time, total_bytes});
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void add(size_t count) {
|
||||
m_measurements.emplace_back(measurement_internal{std::chrono::steady_clock::now(), count});
|
||||
}
|
||||
|
||||
void sub(size_t count) {
|
||||
// overflow, but it's ok
|
||||
m_measurements.emplace_back(measurement_internal{std::chrono::steady_clock::now(), 0U - count});
|
||||
}
|
||||
|
||||
void save(std::filesystem::path const& filename) const {
|
||||
auto fout = std::ofstream(filename);
|
||||
each_measurement([&](measurement m) {
|
||||
fmt::print(fout, "{}; {}\n", std::chrono::duration<double>(m.m_duration).count(), m.m_num_bytes_allocated);
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] auto size() const -> size_t {
|
||||
return m_measurements.size();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
m_measurements.clear();
|
||||
m_start = std::chrono::steady_clock::now();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Forwards all allocations/deallocations to the counts
|
||||
*/
|
||||
template <class T>
|
||||
class counting_allocator {
|
||||
counts_for_allocator* m_counts;
|
||||
|
||||
template <typename U>
|
||||
friend class counting_allocator;
|
||||
|
||||
public:
|
||||
using value_type = T;
|
||||
|
||||
/**
|
||||
* Not explicit so we can easily construct it with the correct resource
|
||||
*/
|
||||
counting_allocator(counts_for_allocator* counts) noexcept // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
|
||||
: m_counts(counts) {}
|
||||
|
||||
/**
|
||||
* Not explicit so we can easily construct it with the correct resource
|
||||
*/
|
||||
template <class U>
|
||||
// NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions)
|
||||
counting_allocator(counting_allocator<U> const& other) noexcept
|
||||
: m_counts(other.m_counts) {}
|
||||
|
||||
counting_allocator(counting_allocator const& other) noexcept = default;
|
||||
counting_allocator(counting_allocator&& other) noexcept = default;
|
||||
auto operator=(counting_allocator const& other) noexcept -> counting_allocator& = default;
|
||||
auto operator=(counting_allocator&& other) noexcept -> counting_allocator& = default;
|
||||
~counting_allocator() = default;
|
||||
|
||||
auto allocate(size_t n) -> T* {
|
||||
m_counts->add(sizeof(T) * n);
|
||||
return std::allocator<T>{}.allocate(n);
|
||||
}
|
||||
|
||||
void deallocate(T* p, size_t n) noexcept {
|
||||
m_counts->sub(sizeof(T) * n);
|
||||
std::allocator<T>{}.deallocate(p, n);
|
||||
}
|
||||
|
||||
template <class U>
|
||||
friend auto operator==(counting_allocator const& a, counting_allocator<U> const& b) noexcept -> bool {
|
||||
return a.m_counts == b.m_counts;
|
||||
}
|
||||
|
||||
template <class U>
|
||||
friend auto operator!=(counting_allocator const& a, counting_allocator<U> const& b) noexcept -> bool {
|
||||
return a.m_counts != b.m_counts;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
||||
#include <doctest.h>
|
||||
|
||||
namespace doctest {
|
||||
|
||||
[[nodiscard]] auto current_test_name() -> char const* {
|
||||
return doctest::detail::g_cs->currentTest->m_name;
|
||||
}
|
||||
|
||||
} // namespace doctest
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
#pragma once
|
||||
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <app/counter.h>
|
||||
|
||||
#include <doctest.h>
|
||||
|
||||
#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
|
||||
# undef DOCTEST_REQUIRE
|
||||
# define DOCTEST_REQUIRE(...) \
|
||||
do { \
|
||||
if (!(__VA_ARGS__)) { \
|
||||
std::abort(); \
|
||||
} \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
namespace doctest {
|
||||
|
||||
[[nodiscard]] auto current_test_name() -> char const*;
|
||||
|
||||
} // namespace doctest
|
||||
|
||||
#include <deque>
|
||||
#include <sstream>
|
||||
|
||||
template <class Key,
|
||||
class T,
|
||||
class Hash = ankerl::unordered_dense::hash<Key>,
|
||||
class KeyEqual = std::equal_to<Key>,
|
||||
class AllocatorOrContainer = std::deque<std::pair<Key, T>>,
|
||||
class Bucket = ankerl::unordered_dense::bucket_type::standard>
|
||||
class deque_map : public ankerl::unordered_dense::detail::table<Key, T, Hash, KeyEqual, AllocatorOrContainer, Bucket, false> {
|
||||
using base_t = ankerl::unordered_dense::detail::table<Key, T, Hash, KeyEqual, AllocatorOrContainer, Bucket, false>;
|
||||
using base_t::base_t;
|
||||
};
|
||||
|
||||
template <class Key,
|
||||
class Hash = ankerl::unordered_dense::hash<Key>,
|
||||
class KeyEqual = std::equal_to<Key>,
|
||||
class AllocatorOrContainer = std::deque<Key>,
|
||||
class Bucket = ankerl::unordered_dense::bucket_type::standard>
|
||||
class deque_set
|
||||
: public ankerl::unordered_dense::detail::table<Key, void, Hash, KeyEqual, AllocatorOrContainer, Bucket, false> {
|
||||
using base_t = ankerl::unordered_dense::detail::table<Key, void, Hash, KeyEqual, AllocatorOrContainer, Bucket, false>;
|
||||
using base_t::base_t;
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define TEST_CASE_MAP(name, ...) \
|
||||
TEST_CASE_TEMPLATE(name, \
|
||||
map_t, \
|
||||
ankerl::unordered_dense::map<__VA_ARGS__>, \
|
||||
ankerl::unordered_dense::segmented_map<__VA_ARGS__>, \
|
||||
deque_map<__VA_ARGS__>)
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define TEST_CASE_SET(name, ...) \
|
||||
TEST_CASE_TEMPLATE(name, \
|
||||
set_t, \
|
||||
ankerl::unordered_dense::set<__VA_ARGS__>, \
|
||||
ankerl::unordered_dense::segmented_set<__VA_ARGS__>, \
|
||||
deque_set<__VA_ARGS__>)
|
||||
|
||||
#define TYPE_TO_STRING_MAP(...) /*NOLINT*/ \
|
||||
TYPE_TO_STRING(ankerl::unordered_dense::map<__VA_ARGS__>); /*NOLINT*/ \
|
||||
TYPE_TO_STRING(ankerl::unordered_dense::segmented_map<__VA_ARGS__>); /*NOLINT*/ \
|
||||
TYPE_TO_STRING(deque_map<__VA_ARGS__>) /*NOLINT*/
|
||||
|
||||
#define TYPE_TO_STRING_SET(...) /*NOLINT*/ \
|
||||
TYPE_TO_STRING(ankerl::unordered_dense::set<__VA_ARGS__>); /*NOLINT*/ \
|
||||
TYPE_TO_STRING(ankerl::unordered_dense::segmented_set<__VA_ARGS__>); /*NOLINT*/ \
|
||||
TYPE_TO_STRING(deque_set<__VA_ARGS__>) /*NOLINT*/
|
||||
|
||||
#if defined(ANKERL_UNORDERED_DENSE_PMR)
|
||||
|
||||
// unfortunately there's no std::experimental::pmr::deque on macos, so just skip this here
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
# define TEST_CASE_PMR_MAP(name, ...) \
|
||||
TEST_CASE_TEMPLATE(name, \
|
||||
map_t, \
|
||||
ankerl::unordered_dense::pmr::map<__VA_ARGS__>, \
|
||||
ankerl::unordered_dense::pmr::segmented_map<__VA_ARGS__>)
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
# define TEST_CASE_PMR_SET(name, ...) \
|
||||
TEST_CASE_TEMPLATE(name, \
|
||||
set_t, \
|
||||
ankerl::unordered_dense::pmr::set<__VA_ARGS__>, \
|
||||
ankerl::unordered_dense::pmr::segmented_set<__VA_ARGS__>)
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
# define TYPE_TO_STRING_PMR_MAP(...) \
|
||||
TYPE_TO_STRING(ankerl::unordered_dense::pmr::map<__VA_ARGS__>); /*NOLINT*/ \
|
||||
TYPE_TO_STRING(ankerl::unordered_dense::pmr::segmented_map<__VA_ARGS__>) /*NOLINT*/
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
# define TYPE_TO_STRING_PMR_SET(...) \
|
||||
TYPE_TO_STRING(ankerl::unordered_dense::pmr::set<__VA_ARGS__>); /*NOLINT*/ \
|
||||
TYPE_TO_STRING(ankerl::unordered_dense::pmr::segmented_set<__VA_ARGS__>) /*NOLINT*/
|
||||
|
||||
#endif
|
||||
|
||||
// adds the most important type to strings here
|
||||
|
||||
TYPE_TO_STRING_MAP(counter::obj, counter::obj);
|
||||
TYPE_TO_STRING_MAP(int, char const*);
|
||||
TYPE_TO_STRING_MAP(int, int);
|
||||
TYPE_TO_STRING_MAP(int, std::string);
|
||||
TYPE_TO_STRING_MAP(std::string, size_t);
|
||||
TYPE_TO_STRING_MAP(std::string, std::string);
|
||||
TYPE_TO_STRING_MAP(uint64_t, uint64_t);
|
||||
TYPE_TO_STRING_MAP(uint32_t, int);
|
||||
TYPE_TO_STRING_MAP(uint64_t, int);
|
||||
TYPE_TO_STRING_SET(counter::obj);
|
||||
TYPE_TO_STRING_SET(int);
|
||||
TYPE_TO_STRING_SET(std::string);
|
||||
TYPE_TO_STRING_SET(uint32_t);
|
||||
TYPE_TO_STRING_SET(uint64_t);
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
|
||||
template <typename It, typename Op>
|
||||
[[nodiscard]] auto geomean(It begin, It end, Op op) -> double {
|
||||
double sum = 0.0;
|
||||
size_t count = 0;
|
||||
while (begin != end) {
|
||||
sum += std::log(op(*begin));
|
||||
++begin;
|
||||
++count;
|
||||
}
|
||||
|
||||
sum /= static_cast<double>(count);
|
||||
return std::exp(sum);
|
||||
}
|
||||
|
||||
template <typename Container, typename Op>
|
||||
[[nodiscard]] auto geomean(Container&& c, Op op) -> double {
|
||||
return geomean(std::begin(c), std::end(c), std::move(op));
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] constexpr auto name_of_type_raw() -> std::string_view {
|
||||
#if defined(_MSC_VER)
|
||||
return __FUNCSIG__; // NOLINT
|
||||
#else
|
||||
return __PRETTY_FUNCTION__; // NOLINT
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] constexpr auto name_of_type() -> std::string_view {
|
||||
using namespace std::literals;
|
||||
|
||||
// idea from https://github.com/TheLartians/StaticTypeInfo/blob/master/include/static_type_info/type_name.h
|
||||
auto for_double = detail::name_of_type_raw<double>();
|
||||
auto n_before = for_double.find("double"sv);
|
||||
auto n_after = for_double.size() - (n_before + "double"sv.size());
|
||||
|
||||
auto str = detail::name_of_type_raw<T>();
|
||||
str.remove_prefix(n_before);
|
||||
str.remove_suffix(n_after);
|
||||
return str;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
#define ANKERL_NANOBENCH_IMPLEMENT
|
||||
#include <third-party/nanobench.h>
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
namespace test {
|
||||
|
||||
template <typename... Args>
|
||||
constexpr void print(fmt::format_string<Args...> f, Args&&... args) {
|
||||
fmt::print(f, std::forward<Args>(args)...);
|
||||
(void)std::fflush(stdout);
|
||||
}
|
||||
|
||||
#ifndef ENABLE_LOG_LINE
|
||||
# define LOG_LINE(what)
|
||||
#else
|
||||
# define LOG_LINE(what) ::test::print("{}({:3}) {}\n", __FILE__, __LINE__, what)
|
||||
#endif
|
||||
|
||||
} // namespace test
|
||||
@@ -0,0 +1,41 @@
|
||||
#if __GNUC__
|
||||
|
||||
# include <fmt/format.h>
|
||||
|
||||
# include <array>
|
||||
# include <csignal>
|
||||
# include <cstdio>
|
||||
# include <cstdlib>
|
||||
# include <execinfo.h>
|
||||
# include <unistd.h>
|
||||
|
||||
namespace {
|
||||
|
||||
void handle(int sig) {
|
||||
fmt::print(stderr, "Error: signal {}:\n", sig);
|
||||
auto ary = std::array<void*, 50>();
|
||||
|
||||
// get void*'s for all entries on the stack
|
||||
auto size = backtrace(ary.data(), static_cast<int>(ary.size()));
|
||||
|
||||
// print out all the frames to stderr
|
||||
fmt::print(stderr, "Error: signal {}. See stacktrace with\n", sig);
|
||||
fmt::print(stderr, "addr2line -Cafpie ./test/udm-test");
|
||||
for (size_t i = 0; i < static_cast<size_t>(size); ++i) {
|
||||
fmt::print(stderr, " {}", ary[i]);
|
||||
}
|
||||
exit(1); // NOLINT(concurrency-mt-unsafe)
|
||||
}
|
||||
|
||||
class handler {
|
||||
public:
|
||||
handler() {
|
||||
(void)signal(SIGTERM, handle);
|
||||
}
|
||||
};
|
||||
|
||||
auto const global_h = handler();
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,17 @@
|
||||
#include "periodic.h"
|
||||
|
||||
namespace ui {
|
||||
|
||||
periodic::periodic(std::chrono::steady_clock::duration interval)
|
||||
: m_interval(interval) {}
|
||||
|
||||
periodic::operator bool() {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
if (now < m_next) {
|
||||
return false;
|
||||
}
|
||||
m_next = now + m_interval;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace ui {
|
||||
|
||||
class periodic {
|
||||
std::chrono::steady_clock::time_point m_next{};
|
||||
std::chrono::steady_clock::duration m_interval{};
|
||||
|
||||
public:
|
||||
explicit periodic(std::chrono::steady_clock::duration interval);
|
||||
explicit operator bool();
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
@@ -0,0 +1,53 @@
|
||||
#include "progress_bar.h"
|
||||
|
||||
#include <algorithm> // for min
|
||||
|
||||
namespace {
|
||||
|
||||
auto split(std::string_view symbols, char sep) -> std::vector<std::string> {
|
||||
auto s = std::vector<std::string>();
|
||||
while (true) {
|
||||
auto idx = symbols.find(sep);
|
||||
if (idx == std::string_view::npos) {
|
||||
break;
|
||||
}
|
||||
s.emplace_back(symbols.substr(0, idx));
|
||||
symbols.remove_prefix(idx + 1);
|
||||
}
|
||||
s.emplace_back(symbols);
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace ui {
|
||||
|
||||
progress_bar::progress_bar(size_t width, size_t total, std::string_view symbols)
|
||||
: m_width(width)
|
||||
, m_total(total)
|
||||
, m_symbols(split(symbols, ' ')) {}
|
||||
|
||||
auto progress_bar::operator()(size_t current) const -> std::string {
|
||||
auto const total_states = m_width * m_symbols.size() + 1;
|
||||
auto const current_state = total_states * current / m_total;
|
||||
std::string str;
|
||||
auto num_full = std::min(m_width, current_state / m_symbols.size());
|
||||
for (size_t i = 0; i < num_full; ++i) {
|
||||
str += m_symbols.back();
|
||||
}
|
||||
|
||||
if (num_full < m_width) {
|
||||
auto remaining = current_state - num_full * m_symbols.size();
|
||||
if (0U != remaining) {
|
||||
str += m_symbols[remaining - 1];
|
||||
}
|
||||
|
||||
auto num_fillers = m_width - num_full - (0U == remaining ? 0 : 1);
|
||||
for (size_t i = 0; i < num_fillers; ++i) {
|
||||
str.push_back(' ');
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <string> // for string, basic_string
|
||||
#include <string_view> // for string_view
|
||||
#include <vector> // for vector
|
||||
|
||||
namespace ui {
|
||||
|
||||
class progress_bar {
|
||||
size_t m_width;
|
||||
size_t m_total;
|
||||
std::vector<std::string> m_symbols;
|
||||
|
||||
public:
|
||||
progress_bar(size_t width, size_t total, std::string_view symbols = "⡀ ⡄ ⡆ ⡇ ⡏ ⡟ ⡿ ⣿");
|
||||
|
||||
auto operator()(size_t current) const -> std::string;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
@@ -0,0 +1,3 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
// this cpp file is only for include-what-you-use
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
#include <ankerl/unordered_dense.h> // for map, operator==
|
||||
|
||||
#include <third-party/nanobench.h> // for Rng, Bench
|
||||
|
||||
#include <app/doctest.h> // for TestCase, skip, TEST_CASE, test_...
|
||||
#include <fmt/core.h> // for format
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <cstdint> // for uint64_t
|
||||
#include <string_view> // for string_view
|
||||
#include <unordered_map> // for unordered_map, operator==
|
||||
|
||||
template <typename Map>
|
||||
void bench(std::string_view name) {
|
||||
auto a = Map();
|
||||
auto rng = ankerl::nanobench::Rng(123);
|
||||
for (size_t i = 0; i < 1000000; ++i) {
|
||||
a.try_emplace(rng(), rng());
|
||||
}
|
||||
|
||||
Map b;
|
||||
ankerl::nanobench::Bench().batch(a.size() * 2).run(fmt::format("copy {}", name), [&] {
|
||||
b = a;
|
||||
a = b;
|
||||
});
|
||||
REQUIRE(a == b);
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
TEST_CASE("bench_copy_rhn" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
bench<robin_hood::unordered_node_map<uint64_t, uint64_t>>("robin_hood::unordered_node_map");
|
||||
}
|
||||
|
||||
TEST_CASE("bench_copy_rhf" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
bench<robin_hood::unordered_flat_map<uint64_t, uint64_t>>("robin_hood::unordered_flat_map");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST_CASE_MAP("bench_copy_udm" * doctest::test_suite("bench") * doctest::skip(), uint64_t, uint64_t) {
|
||||
bench<map_t>("ankerl::unordered_dense::map");
|
||||
}
|
||||
|
||||
TEST_CASE("bench_copy_std" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
bench<std::unordered_map<uint64_t, uint64_t>>("std::unordered_map");
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
#include <ankerl/unordered_dense.h> // for map
|
||||
|
||||
#include <app/name_of_type.h> // for name_of_type
|
||||
#include <third-party/nanobench.h> // for Rng
|
||||
|
||||
#include <app/doctest.h> // for TestCase, skip, ResultBuilder
|
||||
#include <fmt/core.h> // for format, print
|
||||
|
||||
#include <algorithm> // for fill_n
|
||||
#include <array> // for array
|
||||
#include <chrono> // for duration, operator-, steady_clock
|
||||
#include <cstddef> // for size_t
|
||||
#include <unordered_map> // for unordered_map, operator!=
|
||||
#include <vector> // for vector
|
||||
|
||||
template <typename Map>
|
||||
void bench() {
|
||||
static constexpr size_t num_total = 4;
|
||||
|
||||
auto required_checksum = std::array<size_t, 5>{200000, 25198620, 50197240, 75195862, 100194482};
|
||||
auto total = std::chrono::steady_clock::duration();
|
||||
|
||||
for (size_t num_found = 0; num_found < 5; ++num_found) {
|
||||
auto title = fmt::format("random find {}% success {}", num_found * 100 / num_total, name_of_type<Map>());
|
||||
auto rng = ankerl::nanobench::Rng(123);
|
||||
|
||||
size_t checksum = 0;
|
||||
|
||||
using ary_t = std::array<bool, num_total>;
|
||||
auto insert_random = ary_t();
|
||||
insert_random.fill(true);
|
||||
for (typename ary_t::size_type i = 0; i < num_found; ++i) {
|
||||
insert_random[i] = false;
|
||||
}
|
||||
|
||||
auto another_unrelated_rng = ankerl::nanobench::Rng(987654321);
|
||||
auto const another_unrelated_rng_initial_state = another_unrelated_rng.state();
|
||||
auto find_rng = ankerl::nanobench::Rng(another_unrelated_rng_initial_state);
|
||||
|
||||
{
|
||||
static constexpr size_t num_inserts = 200000;
|
||||
static constexpr size_t num_finds_per_insert = 500;
|
||||
static constexpr size_t num_finds_per_iter = num_finds_per_insert * num_total;
|
||||
|
||||
Map map;
|
||||
size_t i = 0;
|
||||
size_t find_count = 0;
|
||||
auto before = std::chrono::steady_clock::now();
|
||||
do {
|
||||
// insert numTotal entries: some random, some sequential.
|
||||
rng.shuffle(insert_random);
|
||||
for (bool const is_random_to_insert : insert_random) {
|
||||
auto val = another_unrelated_rng();
|
||||
if (is_random_to_insert) {
|
||||
map[static_cast<size_t>(rng())] = static_cast<size_t>(1);
|
||||
} else {
|
||||
map[static_cast<size_t>(val)] = static_cast<size_t>(1);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
|
||||
// the actual benchmark code which should be as fast as possible
|
||||
for (size_t j = 0; j < num_finds_per_iter; ++j) {
|
||||
if (++find_count > i) {
|
||||
find_count = 0;
|
||||
find_rng = ankerl::nanobench::Rng(another_unrelated_rng_initial_state);
|
||||
}
|
||||
auto it = map.find(static_cast<size_t>(find_rng()));
|
||||
if (it != map.end()) {
|
||||
checksum += it->second;
|
||||
}
|
||||
}
|
||||
} while (i < num_inserts);
|
||||
checksum += map.size();
|
||||
auto after = std::chrono::steady_clock::now();
|
||||
total += after - before;
|
||||
fmt::print("{}s {}\n", std::chrono::duration<double>(after - before).count(), title);
|
||||
}
|
||||
REQUIRE(checksum == required_checksum[num_found]);
|
||||
}
|
||||
fmt::print("{}s total\n", std::chrono::duration<double>(total).count());
|
||||
}
|
||||
|
||||
// 26.81
|
||||
TEST_CASE("bench_find_random_uo" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
bench<std::unordered_map<size_t, size_t>>();
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
// 10.55
|
||||
TEST_CASE("bench_find_random_rh" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
bench<robin_hood::unordered_flat_map<size_t, size_t>>();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// 8.87
|
||||
TEST_CASE_MAP("bench_find_random_udm" * doctest::test_suite("bench") * doctest::skip(), size_t, size_t) {
|
||||
bench<map_t>();
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
#include <ankerl/unordered_dense.h> // for map
|
||||
|
||||
#include <app/counting_allocator.h>
|
||||
|
||||
#include <app/doctest.h> // for TestCase, skip, ResultBuilder
|
||||
#include <fmt/core.h> // for format, print
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#if __has_include("boost/unordered/unordered_flat_map.hpp")
|
||||
# if defined(__clang__)
|
||||
# pragma clang diagnostic ignored "-Wold-style-cast"
|
||||
# endif
|
||||
# include "boost/unordered/unordered_flat_map.hpp"
|
||||
# define HAS_BOOST_UNORDERED_FLAT_MAP() 1 // NOLINT(cppcoreguidelines-macro-usage)
|
||||
#else
|
||||
# define HAS_BOOST_UNORDERED_FLAT_MAP() 0 // NOLINT(cppcoreguidelines-macro-usage)
|
||||
#endif
|
||||
|
||||
#if 0 && __has_include("absl/container/flat_hash_map.h")
|
||||
# if defined(__clang__)
|
||||
# pragma clang diagnostic ignored "-Wdeprecated-builtins"
|
||||
# pragma clang diagnostic ignored "-Wsign-conversion"
|
||||
# endif
|
||||
# include <absl/container/flat_hash_map.h>
|
||||
# define HAS_ABSL() 1 // NOLINT(cppcoreguidelines-macro-usage)
|
||||
#else
|
||||
# define HAS_ABSL() 0 // NOLINT(cppcoreguidelines-macro-usage)
|
||||
#endif
|
||||
|
||||
class vec2 {
|
||||
uint32_t m_xy;
|
||||
|
||||
public:
|
||||
constexpr vec2(uint16_t x, uint16_t y)
|
||||
: m_xy{static_cast<uint32_t>(x) << 16U | y} {}
|
||||
|
||||
constexpr explicit vec2(uint32_t xy)
|
||||
: m_xy(xy) {}
|
||||
|
||||
[[nodiscard]] constexpr auto pack() const -> uint32_t {
|
||||
return m_xy;
|
||||
};
|
||||
|
||||
[[nodiscard]] constexpr auto add_x(uint16_t x) const -> vec2 {
|
||||
return vec2{m_xy + (static_cast<uint32_t>(x) << 16U)};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto add_y(uint16_t y) const -> vec2 {
|
||||
return vec2{(m_xy & 0xffff0000) | ((m_xy + y) & 0xffff)};
|
||||
}
|
||||
|
||||
template <typename Op>
|
||||
constexpr void for_each_surrounding(Op&& op) const {
|
||||
uint32_t v = m_xy;
|
||||
|
||||
uint32_t upper = (v & 0xffff0000U) - 0x10000;
|
||||
uint32_t l1 = (v - 1) & 0xffffU;
|
||||
uint32_t l2 = v & 0xffffU;
|
||||
uint32_t l3 = (v + 1) & 0xffffU;
|
||||
|
||||
op(upper | l1);
|
||||
op(upper | l2);
|
||||
op(upper | l3);
|
||||
|
||||
upper += 0x10000;
|
||||
op(upper | l1);
|
||||
// op(upper | l2);
|
||||
op(upper | l3);
|
||||
|
||||
upper += 0x10000;
|
||||
op(upper | l1);
|
||||
op(upper | l2);
|
||||
op(upper | l3);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Map>
|
||||
auto game_of_life(std::string_view name, size_t nsteps, Map map1, std::vector<vec2> state) -> size_t {
|
||||
auto before = std::chrono::steady_clock::now();
|
||||
map1.clear();
|
||||
auto map2 = map1; // copy the empty map so we get the allocator
|
||||
|
||||
for (auto& v : state) {
|
||||
v = v.add_x(UINT16_MAX / 2).add_y(UINT16_MAX / 2);
|
||||
map1[v.pack()] = true;
|
||||
v.for_each_surrounding([&](uint32_t xy) {
|
||||
map1.emplace(xy, false);
|
||||
});
|
||||
}
|
||||
|
||||
auto* m1 = &map1;
|
||||
auto* m2 = &map2;
|
||||
for (size_t i = 0; i < nsteps; ++i) {
|
||||
for (auto const& kv : *m1) {
|
||||
auto const& pos = kv.first;
|
||||
auto alive = kv.second;
|
||||
int neighbors = 0;
|
||||
vec2{pos}.for_each_surrounding([&](uint32_t xy) {
|
||||
if (auto x = m1->find(xy); x != m1->end()) {
|
||||
neighbors += x->second;
|
||||
}
|
||||
});
|
||||
if ((alive && (neighbors == 2 || neighbors == 3)) || (!alive && neighbors == 3)) {
|
||||
(*m2)[pos] = true;
|
||||
vec2{pos}.for_each_surrounding([&](uint32_t xy) {
|
||||
m2->emplace(xy, false);
|
||||
});
|
||||
}
|
||||
}
|
||||
m1->clear();
|
||||
std::swap(m1, m2);
|
||||
}
|
||||
|
||||
size_t final_population = 0;
|
||||
for (auto const& kv : *m1) {
|
||||
final_population += kv.second;
|
||||
}
|
||||
auto after = std::chrono::steady_clock::now();
|
||||
fmt::print("{}s {}\n", std::chrono::duration<double>(after - before).count(), name);
|
||||
return final_population;
|
||||
}
|
||||
|
||||
TEST_CASE("gameoflife_gotts-dots" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
// https://conwaylife.com/wiki/Gotts_dots
|
||||
auto state = std::vector<vec2>{
|
||||
{0, 0}, {0, 1}, {0, 2}, // 1
|
||||
{4, 11}, {5, 12}, {6, 13}, {7, 12}, {8, 11}, // 2
|
||||
{9, 13}, {9, 14}, {9, 15}, // 3
|
||||
{185, 24}, {186, 25}, {186, 26}, {186, 27}, {185, 27}, {184, 27}, {183, 27}, {182, 26}, // 4
|
||||
{179, 28}, {180, 29}, {181, 29}, {179, 30}, // 5
|
||||
{182, 32}, {183, 31}, {184, 31}, {185, 31}, {186, 31}, {186, 32}, {186, 33}, {185, 34}, // 6
|
||||
{175, 35}, {176, 36}, {170, 37}, {176, 37}, {171, 38}, {172, 38}, {173, 38}, {174, 38}, {175, 38}, {176, 38}, // 7
|
||||
};
|
||||
|
||||
// size_t nsteps = 200;
|
||||
// size_t nsteps = 2000;
|
||||
size_t nsteps = 4000;
|
||||
// size_t nsteps = 10000;
|
||||
|
||||
auto pop = size_t();
|
||||
{
|
||||
using map_t = ankerl::unordered_dense::map<uint32_t, bool>;
|
||||
pop = game_of_life("ankerl::unordered_dense::map", nsteps, map_t(), state);
|
||||
}
|
||||
{
|
||||
using map_t = ankerl::unordered_dense::segmented_map<uint32_t, bool>;
|
||||
auto new_pop = game_of_life("ankerl::unordered_dense::segmented_map", nsteps, map_t(), state);
|
||||
REQUIRE(pop == new_pop);
|
||||
}
|
||||
|
||||
#if HAS_BOOST_UNORDERED_FLAT_MAP
|
||||
{
|
||||
using map_t = boost::unordered_flat_map<uint32_t, bool>;
|
||||
auto new_pop = game_of_life("boost::unordered_flat_map", nsteps, map_t(), state);
|
||||
REQUIRE(pop == new_pop);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAS_ABSL()
|
||||
{
|
||||
using map_t = absl::flat_hash_map<uint32_t, bool>;
|
||||
auto new_pop = game_of_life("absl::flat_hash_map", nsteps, map_t(), state);
|
||||
REQUIRE(pop == new_pop);
|
||||
}
|
||||
#endif
|
||||
|
||||
{
|
||||
using map_t = std::unordered_map<uint32_t, bool>;
|
||||
auto new_pop = game_of_life("std::unordered_map", nsteps, map_t(), state);
|
||||
REQUIRE(pop == new_pop);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
#include <ankerl/unordered_dense.h> // for map, hash
|
||||
|
||||
#include <app/geomean.h> // for geomean
|
||||
#include <third-party/nanobench.h> // for Rng, doNotOptimizeAway, Bench
|
||||
|
||||
#include <doctest.h> // for TestCase, skip, ResultBuilder
|
||||
#include <fmt/core.h> // for print, format
|
||||
|
||||
#include <chrono> // for duration, operator-, high_resolu...
|
||||
#include <cstdint> // for uint64_t
|
||||
#include <cstring> // for size_t, memcpy
|
||||
#include <deque> // for deque
|
||||
#include <string> // for string, basic_string, operator==
|
||||
#include <string_view> // for string_view, literals
|
||||
#include <unordered_map> // for unordered_map, operator!=
|
||||
#include <vector> // for vector
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename K>
|
||||
inline auto init_key() -> K {
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void randomize_key(ankerl::nanobench::Rng* rng, int n, T* key) {
|
||||
// we limit ourselves to 32bit n
|
||||
auto limited = (((*rng)() >> 32U) * static_cast<uint64_t>(n)) >> 32U;
|
||||
*key = static_cast<T>(limited);
|
||||
}
|
||||
|
||||
template <>
|
||||
[[nodiscard]] inline auto init_key<std::string>() -> std::string {
|
||||
std::string str;
|
||||
str.resize(200);
|
||||
return str;
|
||||
}
|
||||
|
||||
inline void randomize_key(ankerl::nanobench::Rng* rng, int n, std::string* key) {
|
||||
uint64_t k{};
|
||||
randomize_key(rng, n, &k);
|
||||
std::memcpy(key->data(), &k, sizeof(k));
|
||||
}
|
||||
|
||||
// Random insert & erase
|
||||
template <typename Map>
|
||||
void bench_random_insert_erase(ankerl::nanobench::Bench* bench, std::string_view name) {
|
||||
bench->run(fmt::format("{} random insert erase", name), [&] {
|
||||
ankerl::nanobench::Rng rng(123);
|
||||
size_t verifier{};
|
||||
Map map;
|
||||
auto key = init_key<typename Map::key_type>();
|
||||
for (int n = 1; n < 20000; ++n) {
|
||||
for (int i = 0; i < 200; ++i) {
|
||||
randomize_key(&rng, n, &key);
|
||||
map[key];
|
||||
randomize_key(&rng, n, &key);
|
||||
verifier += map.erase(key);
|
||||
}
|
||||
}
|
||||
CHECK(verifier == 1994641U);
|
||||
CHECK(map.size() == 9987U);
|
||||
});
|
||||
}
|
||||
|
||||
// iterate
|
||||
template <typename Map>
|
||||
void bench_iterate(ankerl::nanobench::Bench* bench, std::string_view name) {
|
||||
size_t const num_elements = 5000;
|
||||
|
||||
auto key = init_key<typename Map::key_type>();
|
||||
|
||||
// insert
|
||||
bench->run(fmt::format("{} iterate while adding then removing", name), [&] {
|
||||
ankerl::nanobench::Rng rng(555);
|
||||
Map map;
|
||||
size_t result = 0;
|
||||
for (size_t n = 0; n < num_elements; ++n) {
|
||||
randomize_key(&rng, 1000000, &key);
|
||||
map[key] = n;
|
||||
for (auto const& key_val : map) {
|
||||
result += key_val.second;
|
||||
}
|
||||
}
|
||||
|
||||
rng = ankerl::nanobench::Rng(555);
|
||||
do {
|
||||
randomize_key(&rng, 1000000, &key);
|
||||
map.erase(key);
|
||||
for (auto const& key_val : map) {
|
||||
result += key_val.second;
|
||||
}
|
||||
} while (!map.empty());
|
||||
|
||||
CHECK(result == 62282755409U);
|
||||
});
|
||||
}
|
||||
|
||||
// 111.903 222
|
||||
// 112.023 123123
|
||||
template <typename Map>
|
||||
void bench_random_find(ankerl::nanobench::Bench* bench, std::string_view name) {
|
||||
|
||||
bench->run(fmt::format("{} 50% probability to find", name), [&] {
|
||||
uint64_t const seed = 123123;
|
||||
ankerl::nanobench::Rng numbers_insert_rng(seed);
|
||||
size_t numbers_insert_rng_calls = 0;
|
||||
|
||||
ankerl::nanobench::Rng numbers_search_rng(seed);
|
||||
size_t numbers_search_rng_calls = 0;
|
||||
|
||||
ankerl::nanobench::Rng insertion_rng(123);
|
||||
|
||||
size_t checksum = 0;
|
||||
size_t found = 0;
|
||||
size_t not_found = 0;
|
||||
|
||||
Map map;
|
||||
auto key = init_key<typename Map::key_type>();
|
||||
for (size_t i = 0; i < 100000; ++i) {
|
||||
randomize_key(&numbers_insert_rng, 1000000, &key);
|
||||
++numbers_insert_rng_calls;
|
||||
|
||||
if (insertion_rng() & 1U) {
|
||||
map[key] = i;
|
||||
}
|
||||
|
||||
// search 100 entries in the map
|
||||
for (size_t search = 0; search < 100; ++search) {
|
||||
randomize_key(&numbers_search_rng, 1000000, &key);
|
||||
++numbers_search_rng_calls;
|
||||
|
||||
auto it = map.find(key);
|
||||
if (it != map.end()) {
|
||||
checksum += it->second;
|
||||
++found;
|
||||
} else {
|
||||
++not_found;
|
||||
}
|
||||
if (numbers_insert_rng_calls == numbers_search_rng_calls) {
|
||||
numbers_search_rng = ankerl::nanobench::Rng(seed);
|
||||
numbers_search_rng_calls = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
ankerl::nanobench::doNotOptimizeAway(checksum);
|
||||
ankerl::nanobench::doNotOptimizeAway(found);
|
||||
ankerl::nanobench::doNotOptimizeAway(not_found);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Map>
|
||||
void bench_all(ankerl::nanobench::Bench* bench, std::string_view name) {
|
||||
bench->title("benchmarking");
|
||||
bench->minEpochTime(100ms);
|
||||
bench_iterate<Map>(bench, name);
|
||||
bench_random_insert_erase<Map>(bench, name);
|
||||
bench_random_find<Map>(bench, name);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto geomean1(ankerl::nanobench::Bench const& bench) -> double {
|
||||
return geomean(bench.results(), [](ankerl::nanobench::Result const& result) {
|
||||
return result.median(ankerl::nanobench::Result::Measure::elapsed);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#if 0
|
||||
|
||||
// A relatively quick benchmark that should get a relatively good single number of how good the map
|
||||
// is. It calculates geometric mean of several benchmarks.
|
||||
TEST_CASE("bench_quick_overall_rhf" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
ankerl::nanobench::Bench bench;
|
||||
bench_all<robin_hood::unordered_flat_map<uint64_t, size_t>>(&bench, "robin_hood::unordered_flat_map<uint64_t, size_t>");
|
||||
bench_all<robin_hood::unordered_flat_map<std::string, size_t>>(&bench,
|
||||
"robin_hood::unordered_flat_map<std::string, size_t>");
|
||||
fmt::print("{} bench_quick_overall_rhf\n", geomean1(bench));
|
||||
|
||||
# ifdef ROBIN_HOOD_COUNT_ENABLED
|
||||
std::cout << robin_hood::counts() << std::endl;
|
||||
# endif
|
||||
}
|
||||
|
||||
TEST_CASE("bench_quick_overall_rhn" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
ankerl::nanobench::Bench bench;
|
||||
bench_all<robin_hood::unordered_node_map<uint64_t, size_t>>(&bench, "robin_hood::unordered_node_map<uint64_t, size_t>");
|
||||
bench_all<robin_hood::unordered_node_map<std::string, size_t>>(&bench,
|
||||
"robin_hood::unordered_node_map<std::string, size_t>");
|
||||
fmt::print("{} bench_quick_overall_rhn\n", geomean1(bench));
|
||||
|
||||
# ifdef ROBIN_HOOD_COUNT_ENABLED
|
||||
std::cout << robin_hood::counts() << std::endl;
|
||||
# endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
using hash_t = ankerl::unordered_dense::hash<uint64_t>;
|
||||
using eq_t = std::equal_to<uint64_t>;
|
||||
using pair_t = std::pair<uint64_t, size_t>;
|
||||
|
||||
using hash_str_t = ankerl::unordered_dense::hash<std::string>;
|
||||
using eq_str_t = std::equal_to<std::string>;
|
||||
using pair_str_t = std::pair<std::string, size_t>;
|
||||
|
||||
TEST_CASE("bench_quick_overall_std" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
ankerl::nanobench::Bench bench;
|
||||
bench_all<std::unordered_map<uint64_t, size_t>>(&bench, "std::unordered_map<uint64_t, size_t>");
|
||||
bench_all<std::unordered_map<std::string, size_t>>(&bench, "std::unordered_map<std::string, size_t>");
|
||||
fmt::print("{} bench_quick_overall_map_std\n", geomean1(bench));
|
||||
}
|
||||
|
||||
TEST_CASE("bench_quick_overall_udm" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
ankerl::nanobench::Bench bench;
|
||||
// bench.minEpochTime(1s);
|
||||
|
||||
using map_t = ankerl::unordered_dense::map<uint64_t, size_t>;
|
||||
bench_all<map_t>(&bench, "ankerl::unordered_dense::map<uint64_t, size_t>");
|
||||
|
||||
using map_str_t = ankerl::unordered_dense::map<std::string, size_t, hash_str_t>;
|
||||
bench_all<map_str_t>(&bench, "ankerl::unordered_dense::map<std::string, size_t>");
|
||||
|
||||
fmt::print("{} bench_quick_overall_map_udm\n", geomean1(bench));
|
||||
}
|
||||
|
||||
TEST_CASE("bench_quick_overall_segmented_vector" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
ankerl::nanobench::Bench bench;
|
||||
// bench.minEpochTime(1s);
|
||||
using vec_t = ankerl::unordered_dense::segmented_vector<pair_t>;
|
||||
using map_t = ankerl::unordered_dense::segmented_map<uint64_t, size_t, hash_t, eq_t, vec_t>;
|
||||
bench_all<map_t>(&bench, "ankerl::unordered_dense::map<uint64_t, size_t> segmented_vector");
|
||||
|
||||
using vec_str_t = ankerl::unordered_dense::segmented_vector<pair_str_t>;
|
||||
using map_str_t = ankerl::unordered_dense::map<std::string, size_t, hash_str_t, eq_str_t, vec_str_t>;
|
||||
bench_all<map_str_t>(&bench, "ankerl::unordered_dense::map<std::string, size_t> segmented_vector");
|
||||
|
||||
fmt::print("{} bench_quick_overall_segmented_vector\n", geomean1(bench));
|
||||
}
|
||||
|
||||
TEST_CASE("bench_quick_overall_deque" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
ankerl::nanobench::Bench bench;
|
||||
// bench.minEpochTime(1s);
|
||||
|
||||
using vec_t = std::deque<pair_t>;
|
||||
using map_t = ankerl::unordered_dense::map<uint64_t, size_t, hash_t, eq_t, vec_t>;
|
||||
bench_all<map_t>(&bench, "ankerl::unordered_dense::map<uint64_t, size_t> deque");
|
||||
|
||||
using vec_str_t = std::deque<pair_str_t>;
|
||||
using map_str_t = ankerl::unordered_dense::map<std::string, size_t, hash_str_t, eq_str_t, vec_str_t>;
|
||||
bench_all<map_str_t>(&bench, "ankerl::unordered_dense::map<std::string, size_t> deque");
|
||||
|
||||
fmt::print("{} bench_quick_overall_deque\n", geomean1(bench));
|
||||
}
|
||||
|
||||
TEST_CASE("bench_quick_overall_udm_bigbucket" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
using bucket_t = ankerl::unordered_dense::bucket_type::big;
|
||||
|
||||
ankerl::nanobench::Bench bench;
|
||||
// bench.minEpochTime(1s);
|
||||
|
||||
using alloc_t = std::allocator<pair_t>;
|
||||
using map_t = ankerl::unordered_dense::map<uint64_t, size_t, hash_t, eq_t, alloc_t, bucket_t>;
|
||||
bench_all<map_t>(&bench, "ankerl::unordered_dense::map<uint64_t, size_t>");
|
||||
|
||||
using alloc_str_t = std::allocator<pair_str_t>;
|
||||
using map_str_t = ankerl::unordered_dense::map<std::string, size_t, hash_str_t, eq_str_t, alloc_str_t, bucket_t>;
|
||||
bench_all<map_str_t>(&bench, "ankerl::unordered_dense::map<std::string, size_t>");
|
||||
|
||||
fmt::print("{} bench_quick_overall_map_udm\n", geomean1(bench));
|
||||
}
|
||||
|
||||
template <typename Map>
|
||||
void test_big() {
|
||||
Map map;
|
||||
auto rng = ankerl::nanobench::Rng();
|
||||
for (uint64_t n = 0; n < 20000000; ++n) {
|
||||
map[rng()];
|
||||
map[rng()];
|
||||
map[rng()];
|
||||
map[rng()];
|
||||
}
|
||||
fmt::print("{} map.size()\n", map.size());
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
// 3346376 max RSS, 0:12.40
|
||||
TEST_CASE("memory_map_huge_rhf" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
test_big<robin_hood::unordered_flat_map<uint64_t, size_t>>();
|
||||
}
|
||||
|
||||
// 2616352 max RSS, 0:24.72
|
||||
TEST_CASE("memory_map_huge_rhn" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
test_big<robin_hood::unordered_node_map<uint64_t, size_t>>();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// 3296524 max RSS, 0:50.76
|
||||
TEST_CASE("memory_map_huge_uo" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
test_big<std::unordered_map<uint64_t, size_t>>();
|
||||
}
|
||||
|
||||
// 3149724 max RSS, 0:10.58
|
||||
TEST_CASE("memory_map_huge_udm" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
test_big<ankerl::unordered_dense::map<uint64_t, size_t>>();
|
||||
}
|
||||
|
||||
template <typename Set>
|
||||
void bench_consecutive_insert(char const* name) {
|
||||
auto before = std::chrono::high_resolution_clock::now();
|
||||
Set s{};
|
||||
for (uint64_t x = 0; x < 100000000; ++x) {
|
||||
s.insert(x);
|
||||
}
|
||||
auto after = std::chrono::high_resolution_clock::now();
|
||||
fmt::print("\t{}s, size={} for {}\n", std::chrono::duration<double>(after - before).count(), s.size(), name);
|
||||
}
|
||||
|
||||
template <typename Set>
|
||||
void bench_random_insert(char const* name) {
|
||||
ankerl::nanobench::Rng rng(23);
|
||||
auto before = std::chrono::high_resolution_clock::now();
|
||||
Set s{};
|
||||
for (uint64_t x = 0; x < 100000000; ++x) {
|
||||
s.insert(rng());
|
||||
}
|
||||
auto after = std::chrono::high_resolution_clock::now();
|
||||
fmt::print("\t{}s, size={} for {}\n", std::chrono::duration<double>(after - before).count(), s.size(), name);
|
||||
}
|
||||
|
||||
template <typename Set>
|
||||
void bench_shifted_insert(char const* name) {
|
||||
auto before = std::chrono::high_resolution_clock::now();
|
||||
Set s{};
|
||||
for (uint64_t x = 0; x < 100000000; ++x) {
|
||||
s.insert(x << 4U);
|
||||
}
|
||||
auto after = std::chrono::high_resolution_clock::now();
|
||||
fmt::print("\t{}s, size={} for {}\n", std::chrono::duration<double>(after - before).count(), s.size(), name);
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// #include "absl/container/flat_hash_map.h"
|
||||
#include <ankerl/unordered_dense.h> // for map, operator==
|
||||
|
||||
#include <app/counting_allocator.h>
|
||||
|
||||
#include <third-party/nanobench.h>
|
||||
|
||||
#if __has_include("boost/unordered/unordered_flat_map.hpp")
|
||||
# if defined(__clang__)
|
||||
# pragma clang diagnostic push
|
||||
# pragma clang diagnostic ignored "-Wold-style-cast"
|
||||
# endif
|
||||
# include "boost/unordered/unordered_flat_map.hpp"
|
||||
# define HAS_BOOST_UNORDERED_FLAT_MAP() 1 // NOLINT(cppcoreguidelines-macro-usage)
|
||||
#else
|
||||
# define HAS_BOOST_UNORDERED_FLAT_MAP() 0 // NOLINT(cppcoreguidelines-macro-usage)
|
||||
#endif
|
||||
|
||||
#include <doctest.h>
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
#include <deque>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <unordered_map>
|
||||
|
||||
template <typename Map>
|
||||
void evaluate_map(Map& map) {
|
||||
auto rng = ankerl::nanobench::Rng{1234};
|
||||
|
||||
auto num_elements = size_t{10'000'000};
|
||||
for (uint64_t i = 0; i < num_elements; ++i) {
|
||||
map[rng()] = i;
|
||||
}
|
||||
REQUIRE(map.size() == num_elements);
|
||||
}
|
||||
|
||||
using hash_t = ankerl::unordered_dense::hash<uint64_t>;
|
||||
using eq_t = std::equal_to<uint64_t>;
|
||||
using pair_t = std::pair<uint64_t, uint64_t>;
|
||||
using pair_const_t = std::pair<const uint64_t, uint64_t>;
|
||||
using alloc_t = counting_allocator<pair_t>;
|
||||
using alloc_const_t = counting_allocator<pair_const_t>;
|
||||
|
||||
TEST_CASE("allocated_memory_std_vector" * doctest::skip()) {
|
||||
auto counters = counts_for_allocator{};
|
||||
{
|
||||
using vec_t = std::vector<pair_t, alloc_t>;
|
||||
using map_t = ankerl::unordered_dense::map<uint64_t, uint64_t, hash_t, eq_t, vec_t>;
|
||||
auto map = map_t(0, hash_t{}, eq_t{}, alloc_t{&counters});
|
||||
evaluate_map(map);
|
||||
}
|
||||
counters.save("allocated_memory_std_vector.txt");
|
||||
}
|
||||
|
||||
#if HAS_BOOST_UNORDERED_FLAT_MAP()
|
||||
|
||||
TEST_CASE("allocated_memory_boost_flat_map" * doctest::skip()) {
|
||||
auto counters = counts_for_allocator{};
|
||||
{
|
||||
using map_t = boost::unordered_flat_map<uint64_t, uint64_t, hash_t, eq_t, alloc_t>;
|
||||
auto map = map_t(alloc_t{&counters});
|
||||
evaluate_map(map);
|
||||
}
|
||||
counters.save("allocated_memory_unordered_flat_map.txt");
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("allocated_memory_std_deque" * doctest::skip()) {
|
||||
auto counters = counts_for_allocator{};
|
||||
{
|
||||
using vec_t = std::deque<pair_t, alloc_t>;
|
||||
using map_t = ankerl::unordered_dense::map<uint64_t, uint64_t, hash_t, eq_t, vec_t>;
|
||||
auto map = map_t(0, hash_t{}, eq_t{}, alloc_t{&counters});
|
||||
evaluate_map(map);
|
||||
}
|
||||
counters.save("allocated_memory_std_deque.txt");
|
||||
}
|
||||
|
||||
TEST_CASE("allocated_memory_segmented_vector" * doctest::skip()) {
|
||||
auto counters = counts_for_allocator{};
|
||||
{
|
||||
using vec_t = ankerl::unordered_dense::segmented_vector<pair_t, alloc_t>;
|
||||
using map_t = ankerl::unordered_dense::segmented_map<uint64_t, uint64_t, hash_t, eq_t, vec_t>;
|
||||
auto map = map_t{0, hash_t{}, eq_t{}, alloc_t{&counters}};
|
||||
evaluate_map(map);
|
||||
}
|
||||
counters.save("allocated_memory_segmented_vector.txt");
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
TEST_CASE("allocated_memory_std_unordered_map" * doctest::skip()) {
|
||||
auto counters = counts_for_allocator{};
|
||||
{
|
||||
using map_t = std::unordered_map<uint64_t, uint64_t, hash_t, eq_t, alloc_const_t>;
|
||||
auto map = map_t(0, alloc_t{&counters});
|
||||
evaluate_map(map);
|
||||
}
|
||||
counters.save("allocated_memory_std_unordered_map.txt");
|
||||
}
|
||||
|
||||
TEST_CASE("allocated_memory_boost_unordered_flat_map" * doctest::skip()) {
|
||||
auto counters = counts_for_allocator{};
|
||||
{
|
||||
using map_t = absl::
|
||||
flat_hash_map<uint64_t, uint64_t, absl::container_internal::hash_default_hash<uint64_t>, eq_t, alloc_const_t>;
|
||||
auto map = map_t(0, alloc_t{&counters});
|
||||
evaluate_map(map);
|
||||
}
|
||||
counters.save("allocated_memory_absl_flat_hash_map.txt");
|
||||
}
|
||||
|
||||
#endif
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
#include <ankerl/unordered_dense.h> // for map
|
||||
#include <third-party/nanobench.h> // for Rng, doNotOptimizeAway, Bench
|
||||
|
||||
#include <doctest.h> // for TestCase, skip, TEST_CASE, test_...
|
||||
#include <fmt/core.h> // for format
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <cstdint> // for uint64_t
|
||||
#include <string_view> // for string_view
|
||||
#include <unordered_map> // for unordered_map, swap
|
||||
#include <utility> // for swap
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename Map>
|
||||
void bench(std::string_view name) {
|
||||
Map a;
|
||||
Map b;
|
||||
ankerl::nanobench::Rng rng(123);
|
||||
|
||||
ankerl::nanobench::Bench bench;
|
||||
for (size_t j = 0; j < 10000; ++j) {
|
||||
a[rng()];
|
||||
b[rng()];
|
||||
}
|
||||
bench.run(fmt::format("swap {}", name), [&] {
|
||||
std::swap(a, b);
|
||||
});
|
||||
ankerl::nanobench::doNotOptimizeAway(&a);
|
||||
ankerl::nanobench::doNotOptimizeAway(&b);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#if 0
|
||||
|
||||
TEST_CASE("bench_swap_rhn" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
bench<robin_hood::unordered_node_map<uint64_t, uint64_t>>("robin_hood::unordered_node_map");
|
||||
}
|
||||
|
||||
TEST_CASE("bench_swap_rhf" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
bench<robin_hood::unordered_flat_map<uint64_t, uint64_t>>("robin_hood::unordered_flat_map");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST_CASE("bench_swap_std" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
bench<std::unordered_map<uint64_t, uint64_t>>("std::unordered_map");
|
||||
}
|
||||
|
||||
TEST_CASE("bench_swap_udm" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
bench<ankerl::unordered_dense::map<uint64_t, uint64_t>>("ankerl::unordered_dense::map");
|
||||
}
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
#pragma once
|
||||
|
||||
#include <climits>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
namespace fuzz {
|
||||
|
||||
// Helper to provide a little bit more convenient interface than FuzzedDataProvider itself
|
||||
class provider {
|
||||
uint8_t const* m_data;
|
||||
size_t m_remaining_bytes;
|
||||
|
||||
// Reads one byte and returns a bool, or false when no data remains.
|
||||
[[nodiscard]] inline auto consume_bool() -> bool {
|
||||
return (1U & consume_integral<uint8_t>()) != 0U;
|
||||
}
|
||||
|
||||
// Returns a number in the range [Type's min, Type's max]. The value might
|
||||
// not be uniformly distributed in the given range. If there's no input data
|
||||
// left, always returns |min|.
|
||||
template <typename T>
|
||||
[[nodiscard]] auto consume_integral() -> T {
|
||||
return consume_integral_in_range(std::numeric_limits<T>::min(), std::numeric_limits<T>::max());
|
||||
}
|
||||
|
||||
// Returns a number in the range [min, max] by consuming bytes from the
|
||||
// input data. The value might not be uniformly distributed in the given
|
||||
// range. If there's no input data left, always returns |min|. |min| must
|
||||
// be less than or equal to |max|.
|
||||
template <typename T>
|
||||
[[nodiscard]] auto consume_integral_in_range(T min, T max) -> T {
|
||||
static_assert(std::is_integral<T>::value, "An integral type is required.");
|
||||
static_assert(sizeof(T) <= sizeof(uint64_t), "Unsupported integral type.");
|
||||
|
||||
if (min > max) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
// Use the biggest type possible to hold the range and the result.
|
||||
uint64_t range = static_cast<uint64_t>(max) - static_cast<uint64_t>(min);
|
||||
uint64_t result = 0;
|
||||
size_t offset = 0;
|
||||
|
||||
while (offset < sizeof(T) * CHAR_BIT && (range >> offset) > 0 && m_remaining_bytes != 0) {
|
||||
// Pull bytes off the end of the seed data. Experimentally, this seems to
|
||||
// allow the fuzzer to more easily explore the input space. This makes
|
||||
// sense, since it works by modifying inputs that caused new code to run,
|
||||
// and this data is often used to encode length of data read by
|
||||
// |ConsumeBytes|. Separating out read lengths makes it easier modify the
|
||||
// contents of the data that is actually read.
|
||||
--m_remaining_bytes;
|
||||
result = (result << CHAR_BIT) | m_data[m_remaining_bytes];
|
||||
offset += CHAR_BIT;
|
||||
}
|
||||
|
||||
// Avoid division by 0, in case |range + 1| results in overflow.
|
||||
if (range != std::numeric_limits<decltype(range)>::max()) {
|
||||
result = result % (range + 1);
|
||||
}
|
||||
|
||||
return static_cast<T>(static_cast<uint64_t>(min) + result);
|
||||
}
|
||||
|
||||
inline void advance_unchecked(size_t num_bytes) {
|
||||
m_data += num_bytes;
|
||||
m_remaining_bytes -= num_bytes;
|
||||
}
|
||||
|
||||
// Returns a std::string of length from 0 to |max_length|. When it runs out of
|
||||
// input data, returns what remains of the input. Designed to be more stable
|
||||
// with respect to a fuzzer inserting characters than just picking a random
|
||||
// length and then consuming that many bytes with |ConsumeBytes|.
|
||||
[[nodiscard]] inline auto consume_random_length_string(size_t max_length) -> std::string {
|
||||
// Reads bytes from the start of |data_ptr_|. Maps "\\" to "\", and maps "\"
|
||||
// followed by anything else to the end of the string. As a result of this
|
||||
// logic, a fuzzer can insert characters into the string, and the string
|
||||
// will be lengthened to include those new characters, resulting in a more
|
||||
// stable fuzzer than picking the length of a string independently from
|
||||
// picking its contents.
|
||||
std::string result;
|
||||
|
||||
// Reserve the anticipated capacity to prevent several reallocations.
|
||||
result.reserve(std::min(max_length, m_remaining_bytes));
|
||||
for (size_t i = 0; i < max_length && m_remaining_bytes != 0; ++i) {
|
||||
auto next = m_data[0];
|
||||
advance_unchecked(1);
|
||||
if (next == '\\' && m_remaining_bytes != 0) {
|
||||
next = m_data[0];
|
||||
advance_unchecked(1);
|
||||
if (next != '\\') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
result += static_cast<char>(next);
|
||||
}
|
||||
|
||||
result.shrink_to_fit();
|
||||
return result;
|
||||
}
|
||||
|
||||
provider(provider const&) = default;
|
||||
auto operator=(provider const&) -> provider& = default;
|
||||
|
||||
public:
|
||||
provider(provider&&) = default;
|
||||
auto operator=(provider&&) -> provider& = default;
|
||||
~provider() = default;
|
||||
|
||||
[[nodiscard]] auto copy() const -> provider {
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline explicit provider(void const* data, size_t size)
|
||||
: m_data(reinterpret_cast<uint8_t const*>(data)) /* NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) */
|
||||
, m_remaining_bytes(size) /* NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) */ {}
|
||||
|
||||
// random number in inclusive range [min, max]
|
||||
template <typename T>
|
||||
auto range(T min, T max) -> T {
|
||||
return consume_integral_in_range<T>(min, max);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto bounded(T max_exclusive) -> T {
|
||||
if (0 == max_exclusive) {
|
||||
return {};
|
||||
}
|
||||
return consume_integral_in_range<T>(0, max_exclusive - 1);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto integral() -> T {
|
||||
if constexpr (std::is_same_v<bool, T>) {
|
||||
return consume_bool();
|
||||
} else {
|
||||
return consume_integral<T>();
|
||||
}
|
||||
}
|
||||
|
||||
inline auto string(size_t max_length) -> std::string {
|
||||
return consume_random_length_string(max_length);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
auto pick(Args&&... args) -> std::common_type_t<decltype(args)...>& {
|
||||
static constexpr auto num_ops = sizeof...(args);
|
||||
|
||||
auto idx = size_t{};
|
||||
auto const chosen_idx = consume_integral_in_range<size_t>(0, num_ops - 1);
|
||||
std::common_type_t<decltype(args)...>* result = nullptr;
|
||||
((idx++ == chosen_idx ? (result = &args, true) : false) || ...);
|
||||
return *result;
|
||||
}
|
||||
|
||||
template <typename... Ops>
|
||||
void repeat_oneof(Ops&&... op) {
|
||||
static constexpr auto num_ops = sizeof...(op);
|
||||
|
||||
do {
|
||||
if constexpr (num_ops == 1) {
|
||||
(op(), ...);
|
||||
} else {
|
||||
auto chosen_op_idx = range<size_t>(0, num_ops - 1);
|
||||
auto op_idx = size_t{};
|
||||
((op_idx++ == chosen_op_idx ? op() : void()), ...);
|
||||
}
|
||||
} while (0 != m_remaining_bytes);
|
||||
}
|
||||
|
||||
template <typename... Ops>
|
||||
void limited_repeat_oneof(size_t min, size_t max, Ops&&... op) {
|
||||
static constexpr auto num_ops = sizeof...(op);
|
||||
|
||||
size_t const num_evaluations = consume_integral_in_range(min, max);
|
||||
for (size_t i = 0; i < num_evaluations; ++i) {
|
||||
if constexpr (num_ops == 1) {
|
||||
(op(), ...);
|
||||
} else {
|
||||
auto chosen_op_idx = range<size_t>(0, num_ops - 1);
|
||||
auto op_idx = size_t{};
|
||||
((op_idx++ == chosen_op_idx ? op() : void()), ...);
|
||||
}
|
||||
if (m_remaining_bytes == 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] auto has_remaining_bytes() const -> bool {
|
||||
return 0U != m_remaining_bytes;
|
||||
}
|
||||
|
||||
static inline void require(bool b) {
|
||||
if (!b) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace fuzz
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
#include <fuzz/run.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace fuzz::detail {
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] constexpr auto is_alpha(char c) -> bool {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto is_digit(char c) -> bool {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto is_alnum(char c) -> bool {
|
||||
return is_alpha(c) || is_digit(c);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto contains(std::string_view haystack, char needle) -> bool {
|
||||
return std::string_view::npos != haystack.find_first_of(needle);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto is_valid_filename(std::string_view name) -> bool {
|
||||
using namespace std::literals;
|
||||
for (auto c : name) {
|
||||
if (!is_alnum(c) && !contains("_-+", c)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto env(char const* varname) -> std::optional<std::string> {
|
||||
#ifdef _MSC_VER
|
||||
char* pValue = nullptr;
|
||||
size_t len = 0;
|
||||
errno_t err = _dupenv_s(&pValue, &len, varname);
|
||||
if (err || nullptr == pValue) {
|
||||
return {};
|
||||
}
|
||||
auto str = std::string(pValue);
|
||||
free(pValue);
|
||||
return str;
|
||||
#else
|
||||
char const* val = std::getenv(varname); // NOLINT(concurrency-mt-unsafe,clang-analyzer-cplusplus.StringChecker)
|
||||
if (nullptr == val) {
|
||||
return {};
|
||||
}
|
||||
return val;
|
||||
#endif
|
||||
}
|
||||
|
||||
[[nodiscard]] auto read_file(std::filesystem::path const& p) -> std::optional<std::string> {
|
||||
auto f = std::ifstream(p);
|
||||
if (!f) {
|
||||
return {};
|
||||
}
|
||||
auto content = std::string((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||
if (f.bad()) {
|
||||
return {};
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto find_fuzz_corpus_base_dir() -> std::optional<std::filesystem::path> {
|
||||
auto corpus_base_dir = env("FUZZ_CORPUS_BASE_DIR");
|
||||
if (corpus_base_dir) {
|
||||
return corpus_base_dir.value();
|
||||
}
|
||||
|
||||
auto p = std::filesystem::current_path();
|
||||
while (true) {
|
||||
auto const filename = p / ".fuzz-corpus-base-dir";
|
||||
// INFO(fmt::format("trying '{}'", filename.string()));
|
||||
if (std::filesystem::exists(filename)) {
|
||||
if (auto file_content = read_file(p / ".fuzz-corpus-base-dir"); file_content) {
|
||||
auto f = std::filesystem::path(file_content.value()).make_preferred();
|
||||
// INFO(fmt::format("got it! p='{}, f='{}', p/f='{}'\n", p.string(), f.string(), (p / f).string()));
|
||||
return p / f;
|
||||
}
|
||||
// could not read file
|
||||
throw std::runtime_error(fmt::format("could not read '{}'", filename.string()));
|
||||
}
|
||||
if (p == p.root_path()) {
|
||||
return {};
|
||||
}
|
||||
p = p.parent_path();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void evaluate_corpus(std::function<void(provider)> const& op) {
|
||||
if (!is_valid_filename(doctest::current_test_name())) {
|
||||
throw std::runtime_error("test case name needs to be a valid filename. only [a-zA-Z0-9_-+] are allowed");
|
||||
}
|
||||
|
||||
// 2 ways
|
||||
|
||||
auto corpus_base_dir = find_fuzz_corpus_base_dir();
|
||||
if (!corpus_base_dir) {
|
||||
throw std::runtime_error("could not find corpus base dir :-(");
|
||||
}
|
||||
|
||||
auto path = std::filesystem::path(corpus_base_dir.value()) / doctest::current_test_name();
|
||||
INFO("path=\"" << path.string() << "\"");
|
||||
auto num_files = size_t();
|
||||
for (auto const& dir_entry : std::filesystem::directory_iterator(path)) {
|
||||
++num_files;
|
||||
auto const& test_file = dir_entry.path();
|
||||
CAPTURE(test_file);
|
||||
|
||||
auto f = std::ifstream(test_file);
|
||||
auto content = std::string((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
op(provider(content.data(), content.size()));
|
||||
}
|
||||
REQUIRE(num_files > 1);
|
||||
}
|
||||
|
||||
} // namespace fuzz::detail
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <fuzz/provider.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
|
||||
extern "C" {
|
||||
void HF_ITER(const uint8_t** buf_ptr, size_t* len_ptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace fuzz {
|
||||
|
||||
namespace detail {
|
||||
|
||||
void evaluate_corpus(std::function<void(provider)> const& op);
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* There are 2 modes how this the op() will be executed:
|
||||
*
|
||||
* Driven by honggfuzz: this is enabled when compiling with -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION.
|
||||
* This is done to fuzz a particular test.
|
||||
*
|
||||
* Otherwise, this is run in "corpus" mode, where all files in a directory named by the testname are evaluated
|
||||
* This should be done in normal unit testing. The location of the corpus base directory is determined in this order:
|
||||
* 1. Use FUZZ_CORPUS_BASE_DIR environment variable
|
||||
* 2. If this is not set, look in the working directory for a ".fuzz-corpus-base-dir" file which should contain
|
||||
* the path to the base directory (relative to that particular file)
|
||||
*/
|
||||
template <typename Op>
|
||||
void run(Op const& op) {
|
||||
#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
|
||||
size_t len = 0;
|
||||
uint8_t const* buf = nullptr;
|
||||
while (true) {
|
||||
::HF_ITER(&buf, &len);
|
||||
op(provider(buf, len));
|
||||
}
|
||||
#else
|
||||
detail::evaluate_corpus(op);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace fuzz
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
test_sources = [
|
||||
'app/counter.cpp',
|
||||
'app/doctest.cpp',
|
||||
'app/nanobench.cpp',
|
||||
'app/stacktrace.cpp',
|
||||
'app/ui/periodic.cpp',
|
||||
'app/ui/progress_bar.cpp',
|
||||
'app/unordered_dense.cpp',
|
||||
|
||||
'bench/swap.cpp',
|
||||
'bench/show_allocations.cpp',
|
||||
'bench/quick_overall_map.cpp',
|
||||
'bench/game_of_life.cpp',
|
||||
'bench/find_random.cpp',
|
||||
'bench/copy.cpp',
|
||||
|
||||
'fuzz/run.cpp',
|
||||
|
||||
'unit/assign_to_move.cpp',
|
||||
'unit/assignment_combinations.cpp',
|
||||
'unit/at.cpp',
|
||||
'unit/bucket.cpp',
|
||||
'unit/contains.cpp',
|
||||
'unit/copy_and_assign_maps.cpp',
|
||||
'unit/copyassignment.cpp',
|
||||
'unit/count.cpp',
|
||||
'unit/ctors.cpp',
|
||||
'unit/custom_container_boost.cpp',
|
||||
'unit/custom_container.cpp',
|
||||
'unit/custom_hash.cpp',
|
||||
'unit/deduction_guides.cpp',
|
||||
'unit/diamond.cpp',
|
||||
'unit/empty.cpp',
|
||||
'unit/equal_range.cpp',
|
||||
'unit/erase_if.cpp',
|
||||
'unit/erase_range.cpp',
|
||||
'unit/erase.cpp',
|
||||
'unit/explicit.cpp',
|
||||
'unit/extract.cpp',
|
||||
'unit/fuzz_api.cpp',
|
||||
'unit/fuzz_insert_erase.cpp',
|
||||
'unit/fuzz_replace_map.cpp',
|
||||
'unit/fuzz_string.cpp',
|
||||
'unit/hash_char_types.cpp',
|
||||
'unit/hash_smart_ptr.cpp',
|
||||
'unit/hash_string_view.cpp',
|
||||
'unit/hash.cpp',
|
||||
'unit/include_only.cpp',
|
||||
'unit/initializer_list.cpp',
|
||||
'unit/insert_or_assign.cpp',
|
||||
'unit/insert.cpp',
|
||||
'unit/iterators_empty.cpp',
|
||||
'unit/iterators_erase.cpp',
|
||||
'unit/iterators_insert.cpp',
|
||||
'unit/load_factor.cpp',
|
||||
'unit/maps_of_maps.cpp',
|
||||
'unit/max.cpp',
|
||||
'unit/move_to_moved.cpp',
|
||||
'unit/multiple_apis.cpp',
|
||||
'unit/namespace.cpp',
|
||||
'unit/not_copyable.cpp',
|
||||
'unit/not_moveable.cpp',
|
||||
'unit/pmr_move_with_allocators.cpp',
|
||||
'unit/pmr.cpp',
|
||||
'unit/rehash.cpp',
|
||||
'unit/replace.cpp',
|
||||
'unit/reserve_and_assign.cpp',
|
||||
'unit/reserve.cpp',
|
||||
'unit/segmented_vector.cpp',
|
||||
'unit/set_or_map_types.cpp',
|
||||
'unit/set.cpp',
|
||||
'unit/std_hash.cpp',
|
||||
'unit/swap.cpp',
|
||||
'unit/transparent.cpp',
|
||||
'unit/try_emplace.cpp',
|
||||
'unit/tuple_hash.cpp',
|
||||
'unit/unique_ptr.cpp',
|
||||
'unit/unordered_set.cpp',
|
||||
'unit/vectorofmaps.cpp',
|
||||
'unit/windows_include.cpp',
|
||||
]
|
||||
|
||||
# additional compile options
|
||||
# see https://mesonbuild.com/Reference-tables.html
|
||||
cpp_args = []
|
||||
compiler = meson.get_compiler('cpp')
|
||||
foreach arg : [
|
||||
# gcc
|
||||
'-Wno-stringop-overflow', # g++ error in fmtlib
|
||||
'-Warith-conversion',
|
||||
'-Wshadow=global',
|
||||
'-Wno-array-bounds', # gcc 13 gives incorrect warning
|
||||
|
||||
# gcc / clang
|
||||
'-Wconversion',
|
||||
'-Wextra',
|
||||
'-Wunreachable-code',
|
||||
'-Wuninitialized',
|
||||
'-pedantic-errors',
|
||||
'-Wold-style-cast',
|
||||
'-Wno-unused-function',
|
||||
# '-Weffc++', doesn't work with fmt
|
||||
|
||||
# '-march=native',
|
||||
]
|
||||
if compiler.has_argument(arg)
|
||||
cpp_args += [arg]
|
||||
endif
|
||||
endforeach
|
||||
|
||||
if compiler.get_id() == 'msvc'
|
||||
add_global_arguments(
|
||||
'/wd4189', # fmt: 'zero': local variable is initialized but not referenced, fixed in https://github.com/fmtlib/fmt/issues/2891
|
||||
'/wd4251', # 'fmt::v8::ostream::file_': class 'fmt::v8::file' needs to have dll-interface to be used by clients of class 'fmt::v8::ostream'
|
||||
language: 'cpp')
|
||||
endif
|
||||
|
||||
# for include-what-you-use
|
||||
#cpp_args += '-isystem'
|
||||
#cpp_args += '/usr/lib64/clang/14.0.0/include/'
|
||||
|
||||
fmt_method = 'auto'
|
||||
if get_option('cpp_args').contains('-m32')
|
||||
# use builtin so we can compile it for 32bit.
|
||||
# Can't use it as a default or sanitizer doesn't work...
|
||||
fmt_method = 'builtin'
|
||||
endif
|
||||
|
||||
# use e.g.
|
||||
# CXX='ccache clang++' BOOST_ROOT=/home/martinus/dev/boost_1_81_0/ meson setup --buildtype release -Dcpp_std=c++17 build
|
||||
opt_boost = dependency('boost', required: false)
|
||||
link_args = []
|
||||
if opt_boost.found()
|
||||
add_global_arguments('-DANKERL_UNORDERED_DENSE_HAS_BOOST=1', language: 'cpp')
|
||||
link_args += ['-lrt']
|
||||
else
|
||||
add_global_arguments('-DANKERL_UNORDERED_DENSE_HAS_BOOST=0', language: 'cpp')
|
||||
endif
|
||||
|
||||
#opt_absl = dependency('absl_container', required: true, )
|
||||
#if opt_boost.found()
|
||||
# add_global_arguments('-DANKERL_UNORDERED_DENSE_HAS_ABSL=1', language: 'cpp')
|
||||
#else
|
||||
# add_global_arguments('-DANKERL_UNORDERED_DENSE_HAS_ABSL=0', language: 'cpp')
|
||||
#endif
|
||||
|
||||
cpp_args += [
|
||||
#'-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION',
|
||||
#'-fsanitize-undefined-trap-on-error',
|
||||
#'-fsanitize=undefined,address',
|
||||
#'-fno-sanitize=thread',
|
||||
#'-ftrivial-auto-var-init=pattern',
|
||||
#'-g',
|
||||
]
|
||||
|
||||
link_args += [
|
||||
#'-fsanitize=undefined,address',
|
||||
#'-fno-sanitize=thread'
|
||||
#'-Wl,-shuffle-sections' # for benchmarking with mold linker
|
||||
]
|
||||
|
||||
test_exe = executable(
|
||||
'udm-test',
|
||||
test_sources,
|
||||
include_directories: incdir,
|
||||
cpp_args: cpp_args,
|
||||
link_args: link_args,
|
||||
dependencies: [
|
||||
dependency('threads'), # add dependency for threads (-lpthread, see https://mesonbuild.com/howtox.html),
|
||||
|
||||
# see what's in the [provide] sections for the dependency names
|
||||
dependency('doctest'),
|
||||
dependency('fmt', method: fmt_method),
|
||||
|
||||
# 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'])
|
||||
],
|
||||
)
|
||||
|
||||
benchmark(
|
||||
'bench',
|
||||
test_exe,
|
||||
args: ['-ns', '-ts=bench'],
|
||||
verbose: true)
|
||||
|
||||
test(
|
||||
'unit',
|
||||
test_exe,
|
||||
verbose: true)
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import ankerl.unordered_dense;
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
|
||||
int main() {
|
||||
ankerl::unordered_dense::map<std::string, int> m;
|
||||
m["24535"] = 4;
|
||||
assert(m.size() == 1);
|
||||
|
||||
auto h_int = ankerl::unordered_dense::hash<int>();
|
||||
assert(h_int(123) != 123);
|
||||
|
||||
auto h_str = ankerl::unordered_dense::hash<std::string>();
|
||||
assert(h_str("123") != 123);
|
||||
|
||||
auto h_ptr = ankerl::unordered_dense::hash<int*>();
|
||||
int i = 0;
|
||||
assert(h_ptr(&i) != 0);
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
set -xe
|
||||
|
||||
rm -f *.o *.pcm a.out
|
||||
|
||||
clang++ -std=c++20 -I ../../include --precompile -x c++-module ../../src/ankerl.unordered_dense.cpp
|
||||
clang++ -std=c++20 -c ankerl.unordered_dense.pcm
|
||||
clang++ -std=c++20 -fprebuilt-module-path=. ankerl.unordered_dense.o module_test.cpp -o a.out
|
||||
|
||||
./a.out
|
||||
|
||||
rm -f *.o *.pcm a.out
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
Checks: '-*,
|
||||
bugprone-infinite-loop
|
||||
'
|
||||
WarningsAsErrors: ''
|
||||
HeaderFilterRegex: ''
|
||||
...
|
||||
@@ -0,0 +1,397 @@
|
||||
//===- FuzzedDataProvider.h - Utility header for fuzz targets ---*- C++ -* ===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
// A single header library providing an utility class to break up an array of
|
||||
// bytes. Whenever run on the same input, provides the same output, as long as
|
||||
// its methods are called in the same order, with the same arguments.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_FUZZER_FUZZED_DATA_PROVIDER_H_
|
||||
#define LLVM_FUZZER_FUZZED_DATA_PROVIDER_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <climits>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <initializer_list>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// In addition to the comments below, the API is also briefly documented at
|
||||
// https://github.com/google/fuzzing/blob/master/docs/split-inputs.md#fuzzed-data-provider
|
||||
class FuzzedDataProvider {
|
||||
public:
|
||||
// |data| is an array of length |size| that the FuzzedDataProvider wraps to
|
||||
// provide more granular access. |data| must outlive the FuzzedDataProvider.
|
||||
FuzzedDataProvider(const uint8_t *data, size_t size)
|
||||
: data_ptr_(data), remaining_bytes_(size) {}
|
||||
~FuzzedDataProvider() = default;
|
||||
|
||||
// See the implementation below (after the class definition) for more verbose
|
||||
// comments for each of the methods.
|
||||
|
||||
// Methods returning std::vector of bytes. These are the most popular choice
|
||||
// when splitting fuzzing input into pieces, as every piece is put into a
|
||||
// separate buffer (i.e. ASan would catch any under-/overflow) and the memory
|
||||
// will be released automatically.
|
||||
template <typename T> std::vector<T> ConsumeBytes(size_t num_bytes);
|
||||
template <typename T>
|
||||
std::vector<T> ConsumeBytesWithTerminator(size_t num_bytes, T terminator = 0);
|
||||
template <typename T> std::vector<T> ConsumeRemainingBytes();
|
||||
|
||||
// Methods returning strings. Use only when you need a std::string or a null
|
||||
// terminated C-string. Otherwise, prefer the methods returning std::vector.
|
||||
std::string ConsumeBytesAsString(size_t num_bytes);
|
||||
std::string ConsumeRandomLengthString(size_t max_length);
|
||||
std::string ConsumeRandomLengthString();
|
||||
std::string ConsumeRemainingBytesAsString();
|
||||
|
||||
// Methods returning integer values.
|
||||
template <typename T> T ConsumeIntegral();
|
||||
template <typename T> T ConsumeIntegralInRange(T min, T max);
|
||||
|
||||
// Methods returning floating point values.
|
||||
template <typename T> T ConsumeFloatingPoint();
|
||||
template <typename T> T ConsumeFloatingPointInRange(T min, T max);
|
||||
|
||||
// 0 <= return value <= 1.
|
||||
template <typename T> T ConsumeProbability();
|
||||
|
||||
bool ConsumeBool();
|
||||
|
||||
// Returns a value chosen from the given enum.
|
||||
template <typename T> T ConsumeEnum();
|
||||
|
||||
// Returns a value from the given array.
|
||||
template <typename T, size_t size> T PickValueInArray(const T (&array)[size]);
|
||||
template <typename T, size_t size>
|
||||
T PickValueInArray(const std::array<T, size> &array);
|
||||
template <typename T> T PickValueInArray(std::initializer_list<const T> list);
|
||||
|
||||
// Writes data to the given destination and returns number of bytes written.
|
||||
size_t ConsumeData(void *destination, size_t num_bytes);
|
||||
|
||||
// Reports the remaining bytes available for fuzzed input.
|
||||
size_t remaining_bytes() { return remaining_bytes_; }
|
||||
|
||||
private:
|
||||
FuzzedDataProvider(const FuzzedDataProvider &) = delete;
|
||||
FuzzedDataProvider &operator=(const FuzzedDataProvider &) = delete;
|
||||
|
||||
void CopyAndAdvance(void *destination, size_t num_bytes);
|
||||
|
||||
void Advance(size_t num_bytes);
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> ConsumeBytes(size_t size, size_t num_bytes);
|
||||
|
||||
template <typename TS, typename TU> TS ConvertUnsignedToSigned(TU value);
|
||||
|
||||
const uint8_t *data_ptr_;
|
||||
size_t remaining_bytes_;
|
||||
};
|
||||
|
||||
// Returns a std::vector containing |num_bytes| of input data. If fewer than
|
||||
// |num_bytes| of data remain, returns a shorter std::vector containing all
|
||||
// of the data that's left. Can be used with any byte sized type, such as
|
||||
// char, unsigned char, uint8_t, etc.
|
||||
template <typename T>
|
||||
std::vector<T> FuzzedDataProvider::ConsumeBytes(size_t num_bytes) {
|
||||
num_bytes = std::min(num_bytes, remaining_bytes_);
|
||||
return ConsumeBytes<T>(num_bytes, num_bytes);
|
||||
}
|
||||
|
||||
// Similar to |ConsumeBytes|, but also appends the terminator value at the end
|
||||
// of the resulting vector. Useful, when a mutable null-terminated C-string is
|
||||
// needed, for example. But that is a rare case. Better avoid it, if possible,
|
||||
// and prefer using |ConsumeBytes| or |ConsumeBytesAsString| methods.
|
||||
template <typename T>
|
||||
std::vector<T> FuzzedDataProvider::ConsumeBytesWithTerminator(size_t num_bytes,
|
||||
T terminator) {
|
||||
num_bytes = std::min(num_bytes, remaining_bytes_);
|
||||
std::vector<T> result = ConsumeBytes<T>(num_bytes + 1, num_bytes);
|
||||
result.back() = terminator;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns a std::vector containing all remaining bytes of the input data.
|
||||
template <typename T>
|
||||
std::vector<T> FuzzedDataProvider::ConsumeRemainingBytes() {
|
||||
return ConsumeBytes<T>(remaining_bytes_);
|
||||
}
|
||||
|
||||
// Returns a std::string containing |num_bytes| of input data. Using this and
|
||||
// |.c_str()| on the resulting string is the best way to get an immutable
|
||||
// null-terminated C string. If fewer than |num_bytes| of data remain, returns
|
||||
// a shorter std::string containing all of the data that's left.
|
||||
inline std::string FuzzedDataProvider::ConsumeBytesAsString(size_t num_bytes) {
|
||||
static_assert(sizeof(std::string::value_type) == sizeof(uint8_t),
|
||||
"ConsumeBytesAsString cannot convert the data to a string.");
|
||||
|
||||
num_bytes = std::min(num_bytes, remaining_bytes_);
|
||||
std::string result(
|
||||
reinterpret_cast<const std::string::value_type *>(data_ptr_), num_bytes);
|
||||
Advance(num_bytes);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns a std::string of length from 0 to |max_length|. When it runs out of
|
||||
// input data, returns what remains of the input. Designed to be more stable
|
||||
// with respect to a fuzzer inserting characters than just picking a random
|
||||
// length and then consuming that many bytes with |ConsumeBytes|.
|
||||
inline std::string
|
||||
FuzzedDataProvider::ConsumeRandomLengthString(size_t max_length) {
|
||||
// Reads bytes from the start of |data_ptr_|. Maps "\\" to "\", and maps "\"
|
||||
// followed by anything else to the end of the string. As a result of this
|
||||
// logic, a fuzzer can insert characters into the string, and the string
|
||||
// will be lengthened to include those new characters, resulting in a more
|
||||
// stable fuzzer than picking the length of a string independently from
|
||||
// picking its contents.
|
||||
std::string result;
|
||||
|
||||
// Reserve the anticipated capaticity to prevent several reallocations.
|
||||
result.reserve(std::min(max_length, remaining_bytes_));
|
||||
for (size_t i = 0; i < max_length && remaining_bytes_ != 0; ++i) {
|
||||
char next = ConvertUnsignedToSigned<char>(data_ptr_[0]);
|
||||
Advance(1);
|
||||
if (next == '\\' && remaining_bytes_ != 0) {
|
||||
next = ConvertUnsignedToSigned<char>(data_ptr_[0]);
|
||||
Advance(1);
|
||||
if (next != '\\')
|
||||
break;
|
||||
}
|
||||
result += next;
|
||||
}
|
||||
|
||||
result.shrink_to_fit();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns a std::string of length from 0 to |remaining_bytes_|.
|
||||
inline std::string FuzzedDataProvider::ConsumeRandomLengthString() {
|
||||
return ConsumeRandomLengthString(remaining_bytes_);
|
||||
}
|
||||
|
||||
// Returns a std::string containing all remaining bytes of the input data.
|
||||
// Prefer using |ConsumeRemainingBytes| unless you actually need a std::string
|
||||
// object.
|
||||
inline std::string FuzzedDataProvider::ConsumeRemainingBytesAsString() {
|
||||
return ConsumeBytesAsString(remaining_bytes_);
|
||||
}
|
||||
|
||||
// Returns a number in the range [Type's min, Type's max]. The value might
|
||||
// not be uniformly distributed in the given range. If there's no input data
|
||||
// left, always returns |min|.
|
||||
template <typename T> T FuzzedDataProvider::ConsumeIntegral() {
|
||||
return ConsumeIntegralInRange(std::numeric_limits<T>::min(),
|
||||
std::numeric_limits<T>::max());
|
||||
}
|
||||
|
||||
// Returns a number in the range [min, max] by consuming bytes from the
|
||||
// input data. The value might not be uniformly distributed in the given
|
||||
// range. If there's no input data left, always returns |min|. |min| must
|
||||
// be less than or equal to |max|.
|
||||
template <typename T>
|
||||
T FuzzedDataProvider::ConsumeIntegralInRange(T min, T max) {
|
||||
static_assert(std::is_integral<T>::value, "An integral type is required.");
|
||||
static_assert(sizeof(T) <= sizeof(uint64_t), "Unsupported integral type.");
|
||||
|
||||
if (min > max)
|
||||
abort();
|
||||
|
||||
// Use the biggest type possible to hold the range and the result.
|
||||
uint64_t range = static_cast<uint64_t>(max) - static_cast<uint64_t>(min);
|
||||
uint64_t result = 0;
|
||||
size_t offset = 0;
|
||||
|
||||
while (offset < sizeof(T) * CHAR_BIT && (range >> offset) > 0 &&
|
||||
remaining_bytes_ != 0) {
|
||||
// Pull bytes off the end of the seed data. Experimentally, this seems to
|
||||
// allow the fuzzer to more easily explore the input space. This makes
|
||||
// sense, since it works by modifying inputs that caused new code to run,
|
||||
// and this data is often used to encode length of data read by
|
||||
// |ConsumeBytes|. Separating out read lengths makes it easier modify the
|
||||
// contents of the data that is actually read.
|
||||
--remaining_bytes_;
|
||||
result = (result << CHAR_BIT) | data_ptr_[remaining_bytes_];
|
||||
offset += CHAR_BIT;
|
||||
}
|
||||
|
||||
// Avoid division by 0, in case |range + 1| results in overflow.
|
||||
if (range != std::numeric_limits<decltype(range)>::max())
|
||||
result = result % (range + 1);
|
||||
|
||||
return static_cast<T>(static_cast<uint64_t>(min) + result);
|
||||
}
|
||||
|
||||
// Returns a floating point value in the range [Type's lowest, Type's max] by
|
||||
// consuming bytes from the input data. If there's no input data left, always
|
||||
// returns approximately 0.
|
||||
template <typename T> T FuzzedDataProvider::ConsumeFloatingPoint() {
|
||||
return ConsumeFloatingPointInRange<T>(std::numeric_limits<T>::lowest(),
|
||||
std::numeric_limits<T>::max());
|
||||
}
|
||||
|
||||
// Returns a floating point value in the given range by consuming bytes from
|
||||
// the input data. If there's no input data left, returns |min|. Note that
|
||||
// |min| must be less than or equal to |max|.
|
||||
template <typename T>
|
||||
T FuzzedDataProvider::ConsumeFloatingPointInRange(T min, T max) {
|
||||
if (min > max)
|
||||
abort();
|
||||
|
||||
T range = .0;
|
||||
T result = min;
|
||||
constexpr T zero(.0);
|
||||
if (max > zero && min < zero && max > min + std::numeric_limits<T>::max()) {
|
||||
// The diff |max - min| would overflow the given floating point type. Use
|
||||
// the half of the diff as the range and consume a bool to decide whether
|
||||
// the result is in the first of the second part of the diff.
|
||||
range = (max / 2.0) - (min / 2.0);
|
||||
if (ConsumeBool()) {
|
||||
result += range;
|
||||
}
|
||||
} else {
|
||||
range = max - min;
|
||||
}
|
||||
|
||||
return result + range * ConsumeProbability<T>();
|
||||
}
|
||||
|
||||
// Returns a floating point number in the range [0.0, 1.0]. If there's no
|
||||
// input data left, always returns 0.
|
||||
template <typename T> T FuzzedDataProvider::ConsumeProbability() {
|
||||
static_assert(std::is_floating_point<T>::value,
|
||||
"A floating point type is required.");
|
||||
|
||||
// Use different integral types for different floating point types in order
|
||||
// to provide better density of the resulting values.
|
||||
using IntegralType =
|
||||
typename std::conditional<(sizeof(T) <= sizeof(uint32_t)), uint32_t,
|
||||
uint64_t>::type;
|
||||
|
||||
T result = static_cast<T>(ConsumeIntegral<IntegralType>());
|
||||
result /= static_cast<T>(std::numeric_limits<IntegralType>::max());
|
||||
return result;
|
||||
}
|
||||
|
||||
// Reads one byte and returns a bool, or false when no data remains.
|
||||
inline bool FuzzedDataProvider::ConsumeBool() {
|
||||
return 1 & ConsumeIntegral<uint8_t>();
|
||||
}
|
||||
|
||||
// Returns an enum value. The enum must start at 0 and be contiguous. It must
|
||||
// also contain |kMaxValue| aliased to its largest (inclusive) value. Such as:
|
||||
// enum class Foo { SomeValue, OtherValue, kMaxValue = OtherValue };
|
||||
template <typename T> T FuzzedDataProvider::ConsumeEnum() {
|
||||
static_assert(std::is_enum<T>::value, "|T| must be an enum type.");
|
||||
return static_cast<T>(
|
||||
ConsumeIntegralInRange<uint32_t>(0, static_cast<uint32_t>(T::kMaxValue)));
|
||||
}
|
||||
|
||||
// Returns a copy of the value selected from the given fixed-size |array|.
|
||||
template <typename T, size_t size>
|
||||
T FuzzedDataProvider::PickValueInArray(const T (&array)[size]) {
|
||||
static_assert(size > 0, "The array must be non empty.");
|
||||
return array[ConsumeIntegralInRange<size_t>(0, size - 1)];
|
||||
}
|
||||
|
||||
template <typename T, size_t size>
|
||||
T FuzzedDataProvider::PickValueInArray(const std::array<T, size> &array) {
|
||||
static_assert(size > 0, "The array must be non empty.");
|
||||
return array[ConsumeIntegralInRange<size_t>(0, size - 1)];
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T FuzzedDataProvider::PickValueInArray(std::initializer_list<const T> list) {
|
||||
// TODO(Dor1s): switch to static_assert once C++14 is allowed.
|
||||
if (!list.size())
|
||||
abort();
|
||||
|
||||
return *(list.begin() + ConsumeIntegralInRange<size_t>(0, list.size() - 1));
|
||||
}
|
||||
|
||||
// Writes |num_bytes| of input data to the given destination pointer. If there
|
||||
// is not enough data left, writes all remaining bytes. Return value is the
|
||||
// number of bytes written.
|
||||
// In general, it's better to avoid using this function, but it may be useful
|
||||
// in cases when it's necessary to fill a certain buffer or object with
|
||||
// fuzzing data.
|
||||
inline size_t FuzzedDataProvider::ConsumeData(void *destination,
|
||||
size_t num_bytes) {
|
||||
num_bytes = std::min(num_bytes, remaining_bytes_);
|
||||
CopyAndAdvance(destination, num_bytes);
|
||||
return num_bytes;
|
||||
}
|
||||
|
||||
// Private methods.
|
||||
inline void FuzzedDataProvider::CopyAndAdvance(void *destination,
|
||||
size_t num_bytes) {
|
||||
std::memcpy(destination, data_ptr_, num_bytes);
|
||||
Advance(num_bytes);
|
||||
}
|
||||
|
||||
inline void FuzzedDataProvider::Advance(size_t num_bytes) {
|
||||
if (num_bytes > remaining_bytes_)
|
||||
abort();
|
||||
|
||||
data_ptr_ += num_bytes;
|
||||
remaining_bytes_ -= num_bytes;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> FuzzedDataProvider::ConsumeBytes(size_t size, size_t num_bytes) {
|
||||
static_assert(sizeof(T) == sizeof(uint8_t), "Incompatible data type.");
|
||||
|
||||
// The point of using the size-based constructor below is to increase the
|
||||
// odds of having a vector object with capacity being equal to the length.
|
||||
// That part is always implementation specific, but at least both libc++ and
|
||||
// libstdc++ allocate the requested number of bytes in that constructor,
|
||||
// which seems to be a natural choice for other implementations as well.
|
||||
// To increase the odds even more, we also call |shrink_to_fit| below.
|
||||
std::vector<T> result(size);
|
||||
if (size == 0) {
|
||||
if (num_bytes != 0)
|
||||
abort();
|
||||
return result;
|
||||
}
|
||||
|
||||
CopyAndAdvance(result.data(), num_bytes);
|
||||
|
||||
// Even though |shrink_to_fit| is also implementation specific, we expect it
|
||||
// to provide an additional assurance in case vector's constructor allocated
|
||||
// a buffer which is larger than the actual amount of data we put inside it.
|
||||
result.shrink_to_fit();
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename TS, typename TU>
|
||||
TS FuzzedDataProvider::ConvertUnsignedToSigned(TU value) {
|
||||
static_assert(sizeof(TS) == sizeof(TU), "Incompatible data types.");
|
||||
static_assert(!std::numeric_limits<TU>::is_signed,
|
||||
"Source type must be unsigned.");
|
||||
|
||||
// TODO(Dor1s): change to `if constexpr` once C++17 becomes mainstream.
|
||||
if (std::numeric_limits<TS>::is_modulo)
|
||||
return static_cast<TS>(value);
|
||||
|
||||
// Avoid using implementation-defined unsigned to signed conversions.
|
||||
// To learn more, see https://stackoverflow.com/questions/13150449.
|
||||
if (value <= std::numeric_limits<TS>::max()) {
|
||||
return static_cast<TS>(value);
|
||||
} else {
|
||||
constexpr auto TS_min = std::numeric_limits<TS>::min();
|
||||
return static_cast<TS>(TS_min + static_cast<TS>(value - TS_min));
|
||||
}
|
||||
}
|
||||
|
||||
#endif // LLVM_FUZZER_FUZZED_DATA_PROVIDER_H_
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,84 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#define ENABLE_LOG_LINE
|
||||
#include <app/print.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <type_traits> // for remove_reference, remove_referen...
|
||||
#include <utility> // for move
|
||||
#include <vector> // for vector
|
||||
|
||||
TEST_CASE_MAP("assign_to_moved", int, int) {
|
||||
auto a = map_t();
|
||||
a[1] = 2;
|
||||
auto moved = std::move(a);
|
||||
REQUIRE(moved.size() == 1U);
|
||||
|
||||
auto c = map_t();
|
||||
c[3] = 4;
|
||||
|
||||
// assign to a moved map
|
||||
a = c;
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("move_to_moved", int, int) {
|
||||
auto a = map_t();
|
||||
a[1] = 2;
|
||||
auto moved = std::move(a);
|
||||
|
||||
auto c = map_t();
|
||||
c[3] = 4;
|
||||
|
||||
// assign to a moved map
|
||||
a = std::move(c);
|
||||
|
||||
a[5] = 6;
|
||||
moved[6] = 7;
|
||||
REQUIRE(moved[6] == 7);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("swap", int, int) {
|
||||
{
|
||||
auto b = map_t();
|
||||
{
|
||||
auto a = map_t();
|
||||
b[1] = 2;
|
||||
|
||||
a.swap(b);
|
||||
REQUIRE(a.end() != a.find(1));
|
||||
REQUIRE(b.end() == b.find(1));
|
||||
}
|
||||
REQUIRE(b.end() == b.find(1));
|
||||
b[2] = 3;
|
||||
REQUIRE(b.end() != b.find(2));
|
||||
REQUIRE(b.size() == 1);
|
||||
}
|
||||
|
||||
{
|
||||
auto a = map_t();
|
||||
{
|
||||
auto b = map_t();
|
||||
b[1] = 2;
|
||||
|
||||
a.swap(b);
|
||||
REQUIRE(a.end() != a.find(1));
|
||||
REQUIRE(b.end() == b.find(1));
|
||||
}
|
||||
REQUIRE(a.end() != a.find(1));
|
||||
a[2] = 3;
|
||||
REQUIRE(a.end() != a.find(2));
|
||||
REQUIRE(a.size() == 2);
|
||||
}
|
||||
|
||||
{
|
||||
auto a = map_t();
|
||||
{
|
||||
auto b = map_t();
|
||||
a.swap(b);
|
||||
REQUIRE(a.end() == a.find(1));
|
||||
REQUIRE(b.end() == b.find(1));
|
||||
}
|
||||
REQUIRE(a.end() == a.find(1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#define ENABLE_LOG_LINE
|
||||
#include <app/doctest.h>
|
||||
#include <app/print.h>
|
||||
|
||||
#include <cstdint> // for uint64_t
|
||||
#include <utility> // for pair
|
||||
#include <vector> // for vector
|
||||
|
||||
TEST_CASE_MAP("assignment_combinations_1", uint64_t, uint64_t) {
|
||||
map_t a;
|
||||
map_t b;
|
||||
b = a;
|
||||
REQUIRE(b == a);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("assignment_combinations_2", uint64_t, uint64_t) {
|
||||
map_t a;
|
||||
map_t const& a_const = a;
|
||||
map_t b;
|
||||
a[123] = 321;
|
||||
b = a;
|
||||
|
||||
REQUIRE(a.find(123)->second == 321);
|
||||
REQUIRE(a_const.find(123)->second == 321);
|
||||
|
||||
REQUIRE(b.find(123)->second == 321);
|
||||
a[123] = 111;
|
||||
REQUIRE(a.find(123)->second == 111);
|
||||
REQUIRE(a_const.find(123)->second == 111);
|
||||
REQUIRE(b.find(123)->second == 321);
|
||||
b[123] = 222;
|
||||
REQUIRE(a.find(123)->second == 111);
|
||||
REQUIRE(a_const.find(123)->second == 111);
|
||||
REQUIRE(b.find(123)->second == 222);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("assignment_combinations_3", uint64_t, uint64_t) {
|
||||
map_t a;
|
||||
map_t b;
|
||||
a[123] = 321;
|
||||
a.clear();
|
||||
b = a;
|
||||
|
||||
REQUIRE(a.size() == 0);
|
||||
REQUIRE(b.size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("assignment_combinations_4", uint64_t, uint64_t) {
|
||||
map_t a;
|
||||
map_t b;
|
||||
b[123] = 321;
|
||||
b = a;
|
||||
|
||||
REQUIRE(a.size() == 0);
|
||||
REQUIRE(b.size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("assignment_combinations_5", uint64_t, uint64_t) {
|
||||
map_t a;
|
||||
map_t b;
|
||||
b[123] = 321;
|
||||
b.clear();
|
||||
b = a;
|
||||
|
||||
REQUIRE(a.size() == 0);
|
||||
REQUIRE(b.size() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("assignment_combinations_6", uint64_t, uint64_t) {
|
||||
map_t a;
|
||||
a[1] = 2;
|
||||
map_t b;
|
||||
b[3] = 4;
|
||||
b = a;
|
||||
|
||||
REQUIRE(a.size() == 1);
|
||||
REQUIRE(b.size() == 1);
|
||||
REQUIRE(b.find(1)->second == 2);
|
||||
a[1] = 123;
|
||||
REQUIRE(a.size() == 1);
|
||||
REQUIRE(b.size() == 1);
|
||||
REQUIRE(b.find(1)->second == 2);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("assignment_combinations_7", uint64_t, uint64_t) {
|
||||
map_t a;
|
||||
a[1] = 2;
|
||||
a.clear();
|
||||
map_t b;
|
||||
REQUIRE(a == b);
|
||||
b[3] = 4;
|
||||
REQUIRE(a != b);
|
||||
b = a;
|
||||
REQUIRE(a == b);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("assignment_combinations_7", uint64_t, uint64_t) {
|
||||
map_t a;
|
||||
a[1] = 2;
|
||||
map_t b;
|
||||
REQUIRE(a != b);
|
||||
b[3] = 4;
|
||||
b.clear();
|
||||
REQUIRE(a != b);
|
||||
b = a;
|
||||
REQUIRE(a == b);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("assignment_combinations_8", uint64_t, uint64_t) {
|
||||
map_t a;
|
||||
a[1] = 2;
|
||||
a.clear();
|
||||
map_t b;
|
||||
b[3] = 4;
|
||||
REQUIRE(a != b);
|
||||
b.clear();
|
||||
REQUIRE(a == b);
|
||||
b = a;
|
||||
REQUIRE(a == b);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("assignment_combinations_9", uint64_t, uint64_t) {
|
||||
map_t a;
|
||||
a[1] = 2;
|
||||
|
||||
// self assignment should work too
|
||||
map_t* b = &a;
|
||||
a = *b;
|
||||
REQUIRE(a == a);
|
||||
REQUIRE(a.size() == 1);
|
||||
REQUIRE(a.find(1) != a.end());
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("assignment_combinations_10", uint64_t, uint64_t) {
|
||||
map_t a;
|
||||
a[1] = 2;
|
||||
map_t b;
|
||||
b[2] = 1;
|
||||
|
||||
// maps have the same number of elements, but are not equal.
|
||||
REQUIRE(!(a == b));
|
||||
REQUIRE(a != b);
|
||||
REQUIRE(b != a);
|
||||
REQUIRE(!(a == b));
|
||||
REQUIRE(!(b == a));
|
||||
|
||||
map_t c;
|
||||
c[1] = 3;
|
||||
REQUIRE(a != c);
|
||||
REQUIRE(c != a);
|
||||
REQUIRE(!(a == c));
|
||||
REQUIRE(!(c == a));
|
||||
|
||||
b.clear();
|
||||
REQUIRE(a != b);
|
||||
REQUIRE(b != a);
|
||||
REQUIRE(!(a == b));
|
||||
REQUIRE(!(b == a));
|
||||
|
||||
map_t empty;
|
||||
REQUIRE(b == empty);
|
||||
REQUIRE(empty == b);
|
||||
REQUIRE(!(b != empty));
|
||||
REQUIRE(!(empty != b));
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <stdexcept> // for out_of_range
|
||||
|
||||
TEST_CASE_MAP("at", int, int) {
|
||||
map_t map;
|
||||
map_t const& cmap = map;
|
||||
|
||||
// NOLINTNEXTLINE(llvm-else-after-return,readability-else-after-return)
|
||||
REQUIRE_THROWS_AS(map.at(123), std::out_of_range);
|
||||
|
||||
// NOLINTNEXTLINE(llvm-else-after-return,readability-else-after-return)
|
||||
REQUIRE_THROWS_AS(static_cast<void>(map.at(0)), std::out_of_range);
|
||||
|
||||
// NOLINTNEXTLINE(llvm-else-after-return,readability-else-after-return)
|
||||
REQUIRE_THROWS_AS(static_cast<void>(cmap.at(123)), std::out_of_range);
|
||||
|
||||
// NOLINTNEXTLINE(llvm-else-after-return,readability-else-after-return)
|
||||
REQUIRE_THROWS_AS(static_cast<void>(cmap.at(0)), std::out_of_range);
|
||||
|
||||
map[123] = 333;
|
||||
REQUIRE(map.at(123) == 333);
|
||||
REQUIRE(cmap.at(123) == 333);
|
||||
|
||||
// NOLINTNEXTLINE(llvm-else-after-return,readability-else-after-return)
|
||||
REQUIRE_THROWS_AS(static_cast<void>(map.at(0)), std::out_of_range);
|
||||
|
||||
// NOLINTNEXTLINE(llvm-else-after-return,readability-else-after-return)
|
||||
REQUIRE_THROWS_AS(static_cast<void>(cmap.at(0)), std::out_of_range);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/counter.h>
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <limits>
|
||||
#include <stdexcept> // for out_of_range
|
||||
|
||||
using map_default_t = ankerl::unordered_dense::map<std::string, size_t>;
|
||||
|
||||
// big bucket type allows 2^64 elements, but has more memory & CPU overhead.
|
||||
using map_big_t = ankerl::unordered_dense::map<std::string,
|
||||
size_t,
|
||||
ankerl::unordered_dense::hash<std::string>,
|
||||
std::equal_to<std::string>,
|
||||
std::allocator<std::pair<std::string, size_t>>,
|
||||
ankerl::unordered_dense::bucket_type::big>;
|
||||
|
||||
static_assert(sizeof(map_default_t::bucket_type) == 8U);
|
||||
static_assert(sizeof(map_big_t::bucket_type) == sizeof(size_t) + 4U);
|
||||
static_assert(map_default_t::max_size() == map_default_t::max_bucket_count());
|
||||
|
||||
#if SIZE_MAX == UINT32_MAX
|
||||
static_assert(map_default_t::max_size() == uint64_t{1} << 31U);
|
||||
static_assert(map_big_t::max_size() == uint64_t{1} << 31U);
|
||||
#else
|
||||
static_assert(map_default_t::max_size() == uint64_t{1} << 32U);
|
||||
static_assert(map_big_t::max_size() == uint64_t{1} << 63U);
|
||||
#endif
|
||||
|
||||
struct bucket_micro {
|
||||
static constexpr uint8_t dist_inc = 1U << 1U; // 1 bits for fingerprint
|
||||
static constexpr uint8_t fingerprint_mask = dist_inc - 1; // 11 bit = 2048 positions for distance
|
||||
|
||||
uint8_t m_dist_and_fingerprint;
|
||||
uint8_t m_value_idx;
|
||||
};
|
||||
|
||||
TYPE_TO_STRING_MAP(counter::obj,
|
||||
counter::obj,
|
||||
ankerl::unordered_dense::hash<counter::obj>,
|
||||
std::equal_to<counter::obj>,
|
||||
std::allocator<std::pair<counter::obj, counter::obj>>,
|
||||
bucket_micro);
|
||||
|
||||
TEST_CASE_MAP("bucket_micro",
|
||||
counter::obj,
|
||||
counter::obj,
|
||||
ankerl::unordered_dense::hash<counter::obj>,
|
||||
std::equal_to<counter::obj>,
|
||||
std::allocator<std::pair<counter::obj, counter::obj>>,
|
||||
bucket_micro) {
|
||||
counter counts;
|
||||
INFO(counts);
|
||||
|
||||
auto map = map_t();
|
||||
INFO("map_t::max_size()=" << map_t::max_size());
|
||||
for (size_t i = 0; i < map_t::max_size(); ++i) {
|
||||
if (i == 255) {
|
||||
INFO("i=" << i);
|
||||
}
|
||||
auto const r = map.try_emplace({i, counts}, i, counts);
|
||||
REQUIRE(r.second);
|
||||
|
||||
auto it = map.find({0, counts});
|
||||
REQUIRE(it != map.end());
|
||||
}
|
||||
// NOLINTNEXTLINE(llvm-else-after-return,readability-else-after-return)
|
||||
REQUIRE_THROWS_AS(map.try_emplace({map_t::max_size(), counts}, map_t::max_size(), counts), std::overflow_error);
|
||||
|
||||
// check that all elements are there
|
||||
REQUIRE(map.size() == map_t::max_size());
|
||||
for (size_t i = 0; i < map_t::max_size(); ++i) {
|
||||
INFO(i);
|
||||
auto it = map.find({i, counts});
|
||||
REQUIRE(it != map.end());
|
||||
REQUIRE(it->first.get() == i);
|
||||
REQUIRE(it->second.get() == i);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <cstdint> // for uint64_t
|
||||
|
||||
TEST_CASE_MAP("contains", uint64_t, uint64_t) {
|
||||
static_assert(std::is_same_v<bool, decltype(map_t{}.contains(1))>);
|
||||
|
||||
auto map = map_t();
|
||||
|
||||
REQUIRE(!map.contains(0));
|
||||
REQUIRE(!map.contains(123));
|
||||
map[123];
|
||||
REQUIRE(!map.contains(0));
|
||||
REQUIRE(map.contains(123));
|
||||
map.clear();
|
||||
REQUIRE(!map.contains(0));
|
||||
REQUIRE(!map.contains(123));
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#define ENABLE_LOG_LINE
|
||||
#include <app/doctest.h>
|
||||
#include <app/print.h>
|
||||
|
||||
#include <utility> // for pair
|
||||
#include <vector> // for vector
|
||||
|
||||
// creates a map with some data in it
|
||||
template <class M>
|
||||
[[nodiscard]] auto create_map(int num_elements) -> M {
|
||||
M m;
|
||||
for (int i = 0; i < num_elements; ++i) {
|
||||
m[static_cast<typename M::key_type>((i + 123) * 7)] = static_cast<typename M::mapped_type>(i);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("copy_and_assign_maps_1", int, int) {
|
||||
auto a = create_map<map_t>(15);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("copy_and_assign_maps_2", int, int) {
|
||||
auto a = create_map<map_t>(100);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("copy_and_assign_maps_3", int, int) {
|
||||
auto a = create_map<map_t>(1);
|
||||
// NOLINTNEXTLINE(performance-unnecessary-copy-initialization)
|
||||
auto b = a;
|
||||
REQUIRE(a == b);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("copy_and_assign_maps_4", int, int) {
|
||||
map_t a;
|
||||
REQUIRE(a.empty());
|
||||
a.clear();
|
||||
REQUIRE(a.empty());
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("copy_and_assign_maps_5", int, int) {
|
||||
auto a = create_map<map_t>(100);
|
||||
// NOLINTNEXTLINE(performance-unnecessary-copy-initialization)
|
||||
auto b = a;
|
||||
REQUIRE(b == a);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("copy_and_assign_maps_6", int, int) {
|
||||
map_t a;
|
||||
a[123] = 321;
|
||||
a.clear();
|
||||
auto const maps = std::vector<map_t>(10, a);
|
||||
|
||||
for (auto const& map : maps) {
|
||||
REQUIRE(map.empty());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("copy_and_assign_maps_7", int, int) {
|
||||
auto const maps = std::vector<map_t>(10);
|
||||
REQUIRE(maps.size() == 10U);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("copy_and_assign_maps_8", int, int) {
|
||||
map_t a;
|
||||
auto const maps = std::vector<map_t>(12, a);
|
||||
REQUIRE(maps.size() == 12U);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("copy_and_assign_maps_9", int, int) {
|
||||
map_t a;
|
||||
a[123] = 321;
|
||||
auto const maps = std::vector<map_t>(10, a);
|
||||
a[123] = 1;
|
||||
|
||||
for (auto const& map : maps) {
|
||||
REQUIRE(map.size() == 1);
|
||||
REQUIRE(map.find(123)->second == 321);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
TEST_CASE_MAP("copyassignment", std::string, std::string) {
|
||||
auto map = map_t();
|
||||
auto tmp = map_t();
|
||||
|
||||
map.emplace("a", "b");
|
||||
map = tmp;
|
||||
map.emplace("c", "d");
|
||||
|
||||
REQUIRE(map.size() == 1);
|
||||
REQUIRE(map["c"] == "d");
|
||||
REQUIRE(map.size() == 1);
|
||||
|
||||
REQUIRE(tmp.size() == 0);
|
||||
|
||||
map["e"] = "f";
|
||||
REQUIRE(map.size() == 2);
|
||||
REQUIRE(tmp.size() == 0);
|
||||
|
||||
tmp["g"] = "h";
|
||||
REQUIRE(map.size() == 2);
|
||||
REQUIRE(tmp.size() == 1);
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
TEST_CASE_MAP("count", int, int) {
|
||||
auto map = map_t();
|
||||
REQUIRE(map.count(123) == 0);
|
||||
REQUIRE(map.count(0) == 0);
|
||||
map[123];
|
||||
REQUIRE(map.count(123) == 1);
|
||||
REQUIRE(map.count(0) == 0);
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/counter.h>
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <utility> // for pair
|
||||
|
||||
// very minimal input iterator
|
||||
// https://en.cppreference.com/w/cpp/named_req/InputIterator#Concept
|
||||
class it {
|
||||
std::pair<counter::obj, counter::obj> m_kv;
|
||||
|
||||
public:
|
||||
it(size_t val, counter& counts)
|
||||
: m_kv({val, counts}, {val, counts}) {}
|
||||
|
||||
auto operator++() -> it& {
|
||||
++m_kv.first.get();
|
||||
++m_kv.second.get();
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto operator*() -> std::pair<counter::obj, counter::obj> const& {
|
||||
return m_kv;
|
||||
}
|
||||
|
||||
auto operator!=(it const& other) const -> bool {
|
||||
return other.m_kv.first.get() != m_kv.first.get() || other.m_kv.second.get() != m_kv.second.get();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE_MAP("ctors_map", counter::obj, counter::obj) {
|
||||
using alloc_t = typename map_t::allocator_type;
|
||||
using hash_t = typename map_t::hasher;
|
||||
using key_eq_t = typename map_t::key_equal;
|
||||
|
||||
auto counts = counter();
|
||||
INFO(counts);
|
||||
|
||||
{ auto m = map_t{}; }
|
||||
{ auto m = map_t{0, alloc_t{}}; }
|
||||
{ auto m = map_t{0, hash_t{}, alloc_t{}}; }
|
||||
{ auto m = map_t{alloc_t{}}; }
|
||||
REQUIRE(counts.dtor() == 0);
|
||||
|
||||
{
|
||||
auto begin_it = it{size_t{0}, counts};
|
||||
auto end_it = it{size_t{10}, counts};
|
||||
auto m = map_t{begin_it, end_it};
|
||||
REQUIRE(m.size() == 10);
|
||||
}
|
||||
{
|
||||
auto begin_it = it{size_t{0}, counts};
|
||||
auto end_it = it{size_t{10}, counts};
|
||||
auto m = map_t{begin_it, end_it, 0, alloc_t{}};
|
||||
REQUIRE(m.size() == 10);
|
||||
}
|
||||
{
|
||||
auto begin_it = it{size_t{0}, counts};
|
||||
auto end_it = it{size_t{10}, counts};
|
||||
auto m = map_t{begin_it, end_it, 0, hash_t{}, alloc_t{}};
|
||||
REQUIRE(m.size() == 10);
|
||||
}
|
||||
{
|
||||
auto begin_it = it{size_t{0}, counts};
|
||||
auto end_it = it{size_t{10}, counts};
|
||||
auto m = map_t{begin_it, end_it, 0, hash_t{}, key_eq_t{}};
|
||||
REQUIRE(m.size() == 10);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("ctor_bucket_count_map", counter::obj, counter::obj) {
|
||||
{
|
||||
auto m = map_t{};
|
||||
// depends on initial bucket count, could also be 0
|
||||
// REQUIRE(m.bucket_count() == 2U);
|
||||
}
|
||||
{
|
||||
auto m = map_t{150U};
|
||||
REQUIRE(m.bucket_count() == 256U);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_SET("ctor_bucket_count_set", int) {
|
||||
auto m = ankerl::unordered_dense::set<int>{{1, 2, 3, 4}, 300U};
|
||||
REQUIRE(m.size() == 4U);
|
||||
REQUIRE(m.bucket_count() == 512U);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <deque>
|
||||
|
||||
static_assert(
|
||||
ankerl::unordered_dense::detail::is_detected_v<ankerl::unordered_dense::detail::detect_iterator, std::deque<int>>);
|
||||
|
||||
static_assert(
|
||||
!ankerl::unordered_dense::detail::is_detected_v<ankerl::unordered_dense::detail::detect_iterator, std::allocator<int>>);
|
||||
|
||||
TEST_CASE_MAP("custom_container",
|
||||
int,
|
||||
std::string,
|
||||
ankerl::unordered_dense::hash<int>,
|
||||
std::equal_to<int>,
|
||||
std::deque<std::pair<int, std::string>>) {
|
||||
auto map = map_t();
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
map[i] = std::to_string(i);
|
||||
}
|
||||
|
||||
REQUIRE(std::is_same_v<std::deque<std::pair<int, std::string>>, typename map_t::value_container_type>);
|
||||
std::deque<std::pair<int, std::string>> const container = std::move(map).extract();
|
||||
|
||||
auto m2 = map_t();
|
||||
// we allow use-after-move
|
||||
m2 = map; // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved)
|
||||
|
||||
auto map2 = map;
|
||||
std::swap(map2, map);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
#if ANKERL_UNORDERED_DENSE_HAS_BOOST
|
||||
|
||||
# if __clang__
|
||||
# pragma clang diagnostic push
|
||||
# pragma clang diagnostic ignored "-Wold-style-cast"
|
||||
# endif
|
||||
# include <ankerl/unordered_dense.h>
|
||||
|
||||
# include <app/doctest.h>
|
||||
|
||||
# include <boost/container/vector.hpp>
|
||||
# include <boost/interprocess/allocators/allocator.hpp>
|
||||
# include <boost/interprocess/allocators/node_allocator.hpp>
|
||||
# include <boost/interprocess/containers/vector.hpp>
|
||||
# include <boost/interprocess/managed_shared_memory.hpp>
|
||||
|
||||
# include <deque>
|
||||
|
||||
// Alias an STL-like allocator of ints that allocates ints from the segment
|
||||
using shmem_allocator =
|
||||
boost::interprocess::allocator<std::pair<int, std::string>, boost::interprocess::managed_shared_memory::segment_manager>;
|
||||
using shmem_vector = boost::interprocess::vector<std::pair<int, std::string>, shmem_allocator>;
|
||||
|
||||
// Remove shared memory on construction and destruction
|
||||
struct shm_remove {
|
||||
shm_remove() {
|
||||
boost::interprocess::shared_memory_object::remove("MySharedMemory");
|
||||
}
|
||||
~shm_remove() {
|
||||
boost::interprocess::shared_memory_object::remove("MySharedMemory");
|
||||
}
|
||||
|
||||
shm_remove(shm_remove const&) = delete;
|
||||
shm_remove(shm_remove&&) = delete;
|
||||
auto operator=(shm_remove const&) -> shm_remove = delete;
|
||||
auto operator=(shm_remove&&) -> shm_remove = delete;
|
||||
};
|
||||
|
||||
TYPE_TO_STRING_MAP(int, std::string, ankerl::unordered_dense::hash<int>, std::equal_to<int>, shmem_vector);
|
||||
|
||||
// See https://www.boost.org/doc/libs/1_80_0/doc/html/interprocess/allocators_containers.html
|
||||
TEST_CASE_TEMPLATE(
|
||||
"boost_container_vector",
|
||||
map_t,
|
||||
ankerl::unordered_dense::map<int, std::string, ankerl::unordered_dense::hash<int>, std::equal_to<int>, shmem_vector>,
|
||||
ankerl::unordered_dense::
|
||||
segmented_map<int, std::string, ankerl::unordered_dense::hash<int>, std::equal_to<int>, shmem_allocator>) {
|
||||
|
||||
auto remover = shm_remove();
|
||||
|
||||
// Create shared memory
|
||||
auto segment = boost::interprocess::managed_shared_memory(boost::interprocess::create_only, "MySharedMemory", 1024 * 1024);
|
||||
auto map = map_t{shmem_allocator{segment.get_segment_manager()}};
|
||||
|
||||
int total = 10000;
|
||||
|
||||
for (int i = 0; i < total; ++i) {
|
||||
map.try_emplace(i, std::to_string(i));
|
||||
}
|
||||
|
||||
REQUIRE(map.size() == static_cast<size_t>(total));
|
||||
for (int i = 0; i < total; ++i) {
|
||||
auto it = map.find(i);
|
||||
REQUIRE(it != map.end());
|
||||
REQUIRE(it->first == i);
|
||||
REQUIRE(it->second == std::to_string(i));
|
||||
}
|
||||
|
||||
map.erase(total + 123);
|
||||
REQUIRE(map.size() == static_cast<size_t>(total));
|
||||
map.erase(29);
|
||||
REQUIRE(map.size() == static_cast<size_t>(total - 1));
|
||||
|
||||
map.emplace(std::pair<int, std::string>(9999999, "hello"));
|
||||
REQUIRE(map.size() == static_cast<size_t>(total));
|
||||
}
|
||||
|
||||
#endif // ANKERL_UNORDERED_DENSE_HAS_BOOST
|
||||
@@ -0,0 +1,94 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace {
|
||||
|
||||
struct id {
|
||||
uint64_t value{}; // NOLINT
|
||||
|
||||
auto operator==(id const& other) const -> bool {
|
||||
return value == other.value;
|
||||
}
|
||||
};
|
||||
|
||||
struct custom_hash_simple {
|
||||
[[nodiscard]] auto operator()(id const& x) const noexcept -> uint64_t {
|
||||
return x.value;
|
||||
}
|
||||
};
|
||||
|
||||
struct custom_hash_avalanching {
|
||||
using is_avalanching = void;
|
||||
|
||||
auto operator()(id const& x) const noexcept -> uint64_t {
|
||||
return ankerl::unordered_dense::detail::wyhash::hash(x.value);
|
||||
}
|
||||
};
|
||||
|
||||
struct point {
|
||||
int x{}; // NOLINT
|
||||
int y{}; // NOLINT
|
||||
|
||||
auto operator==(point const& other) const -> bool {
|
||||
return x == other.x && y == other.y;
|
||||
}
|
||||
};
|
||||
|
||||
struct custom_hash_unique_object_representation {
|
||||
using is_avalanching = void;
|
||||
|
||||
[[nodiscard]] auto operator()(point const& f) const noexcept -> uint64_t {
|
||||
static_assert(std::has_unique_object_representations_v<point>);
|
||||
return ankerl::unordered_dense::detail::wyhash::hash(&f, sizeof(f));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
template <>
|
||||
struct ankerl::unordered_dense::hash<id> {
|
||||
using is_avalanching = void;
|
||||
|
||||
[[nodiscard]] auto operator()(id const& x) const noexcept -> uint64_t {
|
||||
return detail::wyhash::hash(x.value);
|
||||
}
|
||||
};
|
||||
|
||||
TYPE_TO_STRING_SET(id);
|
||||
TYPE_TO_STRING_SET(id, custom_hash_simple);
|
||||
TYPE_TO_STRING_SET(id, custom_hash_avalanching);
|
||||
TYPE_TO_STRING_SET(point, custom_hash_unique_object_representation);
|
||||
|
||||
TEST_CASE_SET("custom_hash_simple", id, custom_hash_simple) {
|
||||
auto set = set_t();
|
||||
set.insert(id{124});
|
||||
}
|
||||
|
||||
TEST_CASE_SET("custom_hash_avalanching", id, custom_hash_avalanching) {
|
||||
auto set = set_t();
|
||||
set.insert(id{124});
|
||||
}
|
||||
|
||||
TEST_CASE_SET("custom_hash_unique", point, custom_hash_unique_object_representation) {
|
||||
auto set = set_t();
|
||||
set.insert(point{123, 321});
|
||||
}
|
||||
|
||||
TEST_CASE_SET("custom_hash_default", id) {
|
||||
auto set = set_t();
|
||||
set.insert(id{124});
|
||||
}
|
||||
|
||||
static_assert(
|
||||
!ankerl::unordered_dense::detail::is_detected_v<ankerl::unordered_dense::detail::detect_avalanching, custom_hash_simple>);
|
||||
|
||||
static_assert(ankerl::unordered_dense::detail::is_detected_v<ankerl::unordered_dense::detail::detect_avalanching,
|
||||
custom_hash_avalanching>);
|
||||
static_assert(ankerl::unordered_dense::detail::is_detected_v<ankerl::unordered_dense::detail::detect_avalanching,
|
||||
custom_hash_unique_object_representation>);
|
||||
|
||||
static_assert(!ankerl::unordered_dense::detail::is_detected_v<ankerl::unordered_dense::detail::detect_avalanching,
|
||||
ankerl::unordered_dense::hash<point>>);
|
||||
@@ -0,0 +1,7 @@
|
||||
#include <doctest.h> // for TestCase, skip, TEST_CASE, test_...
|
||||
|
||||
TEST_CASE("deduction_guide") {
|
||||
// TODO(martinus) not yet possible, only in c++20. See
|
||||
// https://stackoverflow.com/a/41008415
|
||||
// https://en.cppreference.com/w/cpp/language/class_template_argument_deduction
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <vector> // for vector
|
||||
|
||||
// struct that provides both hash and equals operator
|
||||
struct hash_with_equal {
|
||||
auto operator()(int x) const -> size_t {
|
||||
return static_cast<size_t>(x);
|
||||
}
|
||||
|
||||
auto operator()(int a, int b) const -> bool {
|
||||
return a == b;
|
||||
}
|
||||
};
|
||||
|
||||
TYPE_TO_STRING_MAP(int, int, hash_with_equal, hash_with_equal);
|
||||
|
||||
// make sure the map works with the same type (check that it handles the diamond problem)
|
||||
TEST_CASE_MAP("diamond_problem", int, int, hash_with_equal, hash_with_equal) {
|
||||
auto map = map_t();
|
||||
map[1] = 2;
|
||||
REQUIRE(map.size() == 1);
|
||||
REQUIRE(map.find(1) != map.end());
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
TEST_CASE_MAP("empty_map_operations", int, int) {
|
||||
map_t m;
|
||||
|
||||
REQUIRE(m.end() == m.find(123));
|
||||
REQUIRE(m.end() == m.begin());
|
||||
m[32];
|
||||
REQUIRE(m.end() != m.begin());
|
||||
REQUIRE(m.end() == m.find(123));
|
||||
REQUIRE(m.end() != m.find(32));
|
||||
|
||||
m = map_t();
|
||||
REQUIRE(m.end() == m.begin());
|
||||
REQUIRE(m.end() == m.find(123));
|
||||
REQUIRE(m.end() == m.find(32));
|
||||
|
||||
map_t m2(m);
|
||||
REQUIRE(m2.end() == m2.begin());
|
||||
REQUIRE(m2.end() == m2.find(123));
|
||||
REQUIRE(m2.end() == m2.find(32));
|
||||
m2[32];
|
||||
REQUIRE(m2.end() != m2.begin());
|
||||
REQUIRE(m2.end() == m2.find(123));
|
||||
REQUIRE(m2.end() != m2.find(32));
|
||||
|
||||
map_t empty;
|
||||
map_t m3(empty);
|
||||
REQUIRE(m3.end() == m3.begin());
|
||||
REQUIRE(m3.end() == m3.find(123));
|
||||
REQUIRE(m3.end() == m3.find(32));
|
||||
m3[32];
|
||||
REQUIRE(m3.end() != m3.begin());
|
||||
REQUIRE(m3.end() == m3.find(123));
|
||||
REQUIRE(m3.end() != m3.find(32));
|
||||
|
||||
map_t m4(std::move(empty));
|
||||
REQUIRE(m4.count(123) == 0);
|
||||
REQUIRE(m4.end() == m4.begin());
|
||||
REQUIRE(m4.end() == m4.find(123));
|
||||
REQUIRE(m4.end() == m4.find(32));
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <type_traits> // for add_const_t
|
||||
#include <utility> // for pair, as_const
|
||||
#include <vector> // for vector
|
||||
|
||||
TEST_CASE_MAP("equal_range", int, int) {
|
||||
auto map = map_t();
|
||||
// auto map = std::unordered_map<int, int>();
|
||||
|
||||
auto range = map.equal_range(123);
|
||||
REQUIRE(range.first == map.end());
|
||||
REQUIRE(range.second == map.end());
|
||||
|
||||
map.try_emplace(1, 1);
|
||||
range = map.equal_range(123);
|
||||
REQUIRE(range.first == map.end());
|
||||
REQUIRE(range.second == map.end());
|
||||
|
||||
int const x = 1;
|
||||
auto const_range = std::as_const(map).equal_range(x);
|
||||
REQUIRE(const_range.first == map.begin());
|
||||
REQUIRE(const_range.second == map.end());
|
||||
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
map.try_emplace(i, i);
|
||||
}
|
||||
range = map.equal_range(50);
|
||||
auto after_first = ++range.first;
|
||||
REQUIRE(range.second == after_first);
|
||||
REQUIRE(range.second != map.end());
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <third-party/nanobench.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <algorithm> // for all_of
|
||||
#include <cstddef> // for size_t
|
||||
#include <cstdint> // for uint32_t
|
||||
#include <unordered_set> // for unordered_set, operator!=
|
||||
#include <vector> // for vector
|
||||
|
||||
template <typename A, typename B>
|
||||
[[nodiscard]] auto is_eq(A const& a, B const& b) -> bool {
|
||||
if (a.size() != b.size()) {
|
||||
return false;
|
||||
}
|
||||
return std::all_of(a.begin(), a.end(), [&b](auto const& k) {
|
||||
return b.end() != b.find(k);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE_SET("insert_erase_random", uint32_t) {
|
||||
auto uds = set_t();
|
||||
auto us = std::unordered_set<uint32_t>();
|
||||
auto rng = ankerl::nanobench::Rng(123);
|
||||
for (size_t i = 0; i < 10000; ++i) {
|
||||
auto key = rng.bounded(1000);
|
||||
uds.insert(key);
|
||||
us.insert(key);
|
||||
REQUIRE(uds.size() == us.size());
|
||||
|
||||
key = rng.bounded(1000);
|
||||
REQUIRE(uds.erase(key) == us.erase(key));
|
||||
REQUIRE(uds.size() == us.size());
|
||||
}
|
||||
REQUIRE(is_eq(uds, us));
|
||||
auto k = *uds.begin();
|
||||
uds.erase(k);
|
||||
REQUIRE(!is_eq(uds, us));
|
||||
us.erase(k);
|
||||
REQUIRE(is_eq(uds, us));
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("erase", int, int) {
|
||||
auto map = ankerl::unordered_dense::map<int, int>();
|
||||
REQUIRE(0 == map.erase(123));
|
||||
REQUIRE(0 == map.count(0));
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/counter.h>
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <cstdint> // for uint64_t
|
||||
#include <utility> // for pair
|
||||
|
||||
TEST_CASE_SET("erase_if_set", counter::obj) {
|
||||
auto counts = counter();
|
||||
INFO(counts);
|
||||
auto set = set_t();
|
||||
|
||||
for (size_t i = 0; i < 1000; ++i) {
|
||||
set.emplace(i, counts);
|
||||
}
|
||||
REQUIRE(set.size() == 1000);
|
||||
auto num_erased = std::erase_if(set, [](counter::obj const& obj) {
|
||||
return 0 == obj.get() % 3;
|
||||
});
|
||||
REQUIRE(num_erased == 334);
|
||||
|
||||
REQUIRE(set.size() == 666);
|
||||
for (size_t i = 0; i < 1000; ++i) {
|
||||
if (0 == i % 3) {
|
||||
REQUIRE(!set.contains({i, counts}));
|
||||
} else {
|
||||
REQUIRE(set.contains({i, counts}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("erase_if_map", uint64_t, uint64_t) {
|
||||
auto map = map_t();
|
||||
for (size_t i = 0; i < 1000; ++i) {
|
||||
map.try_emplace(i, i);
|
||||
}
|
||||
|
||||
REQUIRE(map.size() == 1000);
|
||||
auto num_erased = std::erase_if(map, [](std::pair<uint64_t, uint64_t> const& x) {
|
||||
return 0 == x.second % 2;
|
||||
});
|
||||
|
||||
REQUIRE(num_erased == 500);
|
||||
REQUIRE(map.size() == 500);
|
||||
for (size_t i = 0; i < 1000; ++i) {
|
||||
if (0 == i % 2) {
|
||||
REQUIRE(!map.contains(i));
|
||||
} else {
|
||||
REQUIRE(map.contains(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/counter.h>
|
||||
#include <app/doctest.h>
|
||||
#include <third-party/nanobench.h>
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <vector> // for vector
|
||||
|
||||
TEST_CASE_MAP("erase_range", counter::obj, counter::obj) {
|
||||
int const num_elements = 10;
|
||||
|
||||
for (int first_pos = 0; first_pos <= num_elements; ++first_pos) {
|
||||
for (int last_pos = first_pos; last_pos <= num_elements; ++last_pos) {
|
||||
auto counts = counter();
|
||||
INFO(counts);
|
||||
|
||||
auto map = map_t();
|
||||
|
||||
for (size_t i = 0; i < num_elements; ++i) {
|
||||
auto key = i;
|
||||
auto val = i * 1000;
|
||||
map.try_emplace({key, counts}, val, counts);
|
||||
}
|
||||
REQUIRE(map.size() == num_elements);
|
||||
|
||||
auto it_ret = map.erase(map.cbegin() + first_pos, map.cbegin() + last_pos);
|
||||
REQUIRE(map.size() == static_cast<size_t>(num_elements - (last_pos - first_pos)));
|
||||
REQUIRE(it_ret == map.begin() + first_pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
struct texture {
|
||||
int m_width;
|
||||
int m_height;
|
||||
void* m_data;
|
||||
};
|
||||
|
||||
struct per_image {
|
||||
std::unordered_map<void*, texture*> m_texture_index;
|
||||
};
|
||||
|
||||
template <typename Map>
|
||||
struct scene {
|
||||
std::vector<per_image> m_per_image;
|
||||
Map m_textures_per_key;
|
||||
};
|
||||
|
||||
template <typename Map>
|
||||
struct app_state {
|
||||
scene<Map> m_scene;
|
||||
};
|
||||
|
||||
TYPE_TO_STRING_MAP(void*, texture*);
|
||||
|
||||
TEST_CASE_MAP("unit_create_AppState_issue_97", void*, texture*) {
|
||||
app_state<map_t> const app_state{};
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/counter.h>
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
TEST_CASE_MAP("extract", counter::obj, counter::obj) {
|
||||
auto counts = counter();
|
||||
INFO(counts);
|
||||
|
||||
auto container = typename map_t::value_container_type();
|
||||
{
|
||||
auto map = map_t();
|
||||
|
||||
for (size_t i = 0; i < 100; ++i) {
|
||||
map.try_emplace(counter::obj{i, counts}, i, counts);
|
||||
}
|
||||
|
||||
container = std::move(map).extract();
|
||||
}
|
||||
|
||||
REQUIRE(container.size() == 100U);
|
||||
for (size_t i = 0; i < container.size(); ++i) {
|
||||
REQUIRE(container[i].first.get() == i);
|
||||
REQUIRE(container[i].second.get() == i);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("extract_element", counter::obj, counter::obj) {
|
||||
auto counts = counter();
|
||||
INFO(counts);
|
||||
|
||||
counts("init");
|
||||
auto map = map_t();
|
||||
for (size_t i = 0; i < 100; ++i) {
|
||||
map.try_emplace(counter::obj{i, counts}, i, counts);
|
||||
}
|
||||
|
||||
// extract(key)
|
||||
for (size_t i = 0; i < 20; ++i) {
|
||||
auto query = counter::obj{i, counts};
|
||||
counts("before remove 1");
|
||||
auto opt = map.extract(query);
|
||||
counts("after remove 1");
|
||||
REQUIRE(opt);
|
||||
REQUIRE(opt->first.get() == i);
|
||||
REQUIRE(opt->second.get() == i);
|
||||
}
|
||||
REQUIRE(map.size() == 80);
|
||||
|
||||
// extract iterator
|
||||
for (size_t i = 20; i < 100; ++i) {
|
||||
auto query = counter::obj{i, counts};
|
||||
auto it = map.find(query);
|
||||
REQUIRE(it != map.end());
|
||||
auto opt = map.extract(it);
|
||||
REQUIRE(opt.first.get() == i);
|
||||
REQUIRE(opt.second.get() == i);
|
||||
}
|
||||
REQUIRE(map.empty());
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <app/doctest.h>
|
||||
#include <fuzz/provider.h>
|
||||
#include <fuzz/run.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
template <typename Map>
|
||||
void do_fuzz_api(fuzz::provider p) {
|
||||
auto counts = counter();
|
||||
auto map = Map();
|
||||
p.repeat_oneof(
|
||||
[&] {
|
||||
auto key = p.integral<size_t>();
|
||||
auto it = map.try_emplace(counter::obj(key, counts), counter::obj(key, counts)).first;
|
||||
REQUIRE(it != map.end());
|
||||
REQUIRE(it->first.get() == key);
|
||||
},
|
||||
[&] {
|
||||
auto key = p.integral<size_t>();
|
||||
map.emplace(std::piecewise_construct, std::forward_as_tuple(key, counts), std::forward_as_tuple(key + 77, counts));
|
||||
},
|
||||
[&] {
|
||||
auto key = p.integral<size_t>();
|
||||
map[counter::obj(key, counts)] = counter::obj(key + 123, counts);
|
||||
},
|
||||
[&] {
|
||||
auto key = p.integral<size_t>();
|
||||
map.insert(std::pair<counter::obj, counter::obj>(counter::obj(key, counts), counter::obj(key, counts)));
|
||||
},
|
||||
[&] {
|
||||
auto key = p.integral<size_t>();
|
||||
map.insert_or_assign(counter::obj(key, counts), counter::obj(key + 1, counts));
|
||||
},
|
||||
[&] {
|
||||
auto key = p.integral<size_t>();
|
||||
map.erase(counter::obj(key, counts));
|
||||
},
|
||||
[&] {
|
||||
map = Map{};
|
||||
},
|
||||
[&] {
|
||||
auto m = Map{};
|
||||
m.swap(map);
|
||||
},
|
||||
[&] {
|
||||
map.clear();
|
||||
},
|
||||
[&] {
|
||||
auto s = p.bounded<size_t>(1025);
|
||||
map.rehash(s);
|
||||
},
|
||||
[&] {
|
||||
auto s = p.bounded<size_t>(1025);
|
||||
map.reserve(s);
|
||||
},
|
||||
[&] {
|
||||
auto key = p.integral<size_t>();
|
||||
auto it = map.find(counter::obj(key, counts));
|
||||
auto d = std::distance(map.begin(), it);
|
||||
REQUIRE(0 <= d);
|
||||
REQUIRE(d <= static_cast<std::ptrdiff_t>(map.size()));
|
||||
},
|
||||
[&] {
|
||||
if (!map.empty()) {
|
||||
auto idx = p.bounded(static_cast<int>(map.size()));
|
||||
auto it = map.cbegin() + idx;
|
||||
auto const& key = it->first;
|
||||
auto found_it = map.find(key);
|
||||
REQUIRE(it == found_it);
|
||||
}
|
||||
},
|
||||
[&] {
|
||||
if (!map.empty()) {
|
||||
auto it = map.begin() + p.bounded(static_cast<int>(map.size()));
|
||||
map.erase(it);
|
||||
}
|
||||
},
|
||||
[&] {
|
||||
auto tmp = Map();
|
||||
std::swap(tmp, map);
|
||||
},
|
||||
[&] {
|
||||
map = std::initializer_list<std::pair<counter::obj, counter::obj>>{
|
||||
{{1, counts}, {2, counts}},
|
||||
{{3, counts}, {4, counts}},
|
||||
{{5, counts}, {6, counts}},
|
||||
};
|
||||
REQUIRE(map.size() == 3);
|
||||
},
|
||||
[&] {
|
||||
auto first_idx = 0;
|
||||
auto last_idx = 0;
|
||||
if (!map.empty()) {
|
||||
first_idx = p.bounded(static_cast<int>(map.size()));
|
||||
last_idx = p.bounded(static_cast<int>(map.size()));
|
||||
if (first_idx > last_idx) {
|
||||
std::swap(first_idx, last_idx);
|
||||
}
|
||||
}
|
||||
map.erase(map.cbegin() + first_idx, map.cbegin() + last_idx);
|
||||
},
|
||||
[&] {
|
||||
map.~Map();
|
||||
counts.check_all_done();
|
||||
new (&map) Map();
|
||||
},
|
||||
[&] {
|
||||
std::erase_if(map, [&](typename Map::value_type const& /*v*/) {
|
||||
return p.integral<bool>();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
TEST_CASE("fuzz_api" * doctest::test_suite("fuzz")) {
|
||||
fuzz::run([](fuzz::provider p) {
|
||||
do_fuzz_api<ankerl::unordered_dense::map<counter::obj, counter::obj>>(p.copy());
|
||||
do_fuzz_api<ankerl::unordered_dense::segmented_map<counter::obj, counter::obj>>(p.copy());
|
||||
do_fuzz_api<deque_map<counter::obj, counter::obj>>(p.copy());
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <fuzz/run.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace {
|
||||
|
||||
// Using DummyHash to make it easier for the fuzzer
|
||||
struct dummy_hash {
|
||||
using is_avalanching = void;
|
||||
|
||||
auto operator()(uint64_t x) const noexcept -> size_t {
|
||||
return static_cast<size_t>(x);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Map>
|
||||
void insert_erase(fuzz::provider p) {
|
||||
auto ank = Map();
|
||||
auto ref = std::unordered_map<uint64_t, uint64_t>();
|
||||
|
||||
auto c = uint64_t();
|
||||
while (p.has_remaining_bytes()) {
|
||||
auto key = p.integral<uint64_t>();
|
||||
ank[key] = c;
|
||||
ref[key] = c;
|
||||
++c;
|
||||
|
||||
key = p.integral<uint64_t>();
|
||||
REQUIRE(ank.erase(key) == ref.erase(key));
|
||||
REQUIRE(ank.size() == ref.size());
|
||||
}
|
||||
auto cpy = std::unordered_map(ank.begin(), ank.end());
|
||||
REQUIRE(cpy == ref);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("fuzz_insert_erase" * doctest::test_suite("fuzz")) {
|
||||
fuzz::run([](fuzz::provider p) {
|
||||
// try all 3 different map styles with the same input
|
||||
insert_erase<ankerl::unordered_dense::map<uint64_t, uint64_t, dummy_hash>>(p.copy());
|
||||
insert_erase<ankerl::unordered_dense::segmented_map<uint64_t, uint64_t, dummy_hash>>(p.copy());
|
||||
insert_erase<ankerl::unordered_dense::map<uint64_t,
|
||||
uint64_t,
|
||||
ankerl::unordered_dense::hash<uint64_t>,
|
||||
std::equal_to<uint64_t>,
|
||||
std::deque<std::pair<uint64_t, uint64_t>>>>(p.copy());
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <app/doctest.h>
|
||||
#include <fuzz/run.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename Map>
|
||||
void replace_map(fuzz::provider p) {
|
||||
auto counts = counter{};
|
||||
|
||||
using map_t = Map;
|
||||
|
||||
auto initial_size = p.bounded<size_t>(100);
|
||||
auto map = map_t{};
|
||||
for (size_t i = 0; i < initial_size; ++i) {
|
||||
map.try_emplace(counter::obj{i, counts}, counter::obj{i, counts});
|
||||
}
|
||||
|
||||
// create a container with data in it provided by fuzzer
|
||||
auto container = typename map_t::value_container_type{};
|
||||
auto comparison_container = std::vector<std::pair<size_t, size_t>>();
|
||||
auto v = size_t{};
|
||||
while (p.has_remaining_bytes()) {
|
||||
auto key = p.integral<size_t>();
|
||||
container.emplace_back(counter::obj{key, counts}, counter::obj{v, counts});
|
||||
comparison_container.emplace_back(key, v);
|
||||
++v;
|
||||
}
|
||||
|
||||
// create comparison map with the same move-back-forward algorithm
|
||||
auto comparison_map = std::unordered_map<size_t, size_t>{};
|
||||
size_t idx = 0;
|
||||
while (idx != comparison_container.size()) {
|
||||
auto [key, val] = comparison_container[idx];
|
||||
if (comparison_map.try_emplace(key, val).second) {
|
||||
++idx;
|
||||
} else {
|
||||
comparison_container[idx] = comparison_container.back();
|
||||
comparison_container.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
map.replace(std::move(container));
|
||||
|
||||
// now check if the data in the map is exactly what we expect
|
||||
REQUIRE(map.size() == comparison_map.size());
|
||||
for (auto [key, val] : comparison_map) {
|
||||
auto key_obj = counter::obj{key, counts};
|
||||
auto val_obj = counter::obj{val, counts};
|
||||
auto it = map.find(key_obj);
|
||||
REQUIRE(it != map.end());
|
||||
REQUIRE(it->first == key_obj);
|
||||
REQUIRE(it->second == val_obj);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("fuzz_replace_map" * doctest::test_suite("fuzz")) {
|
||||
fuzz::run([](fuzz::provider p) {
|
||||
replace_map<ankerl::unordered_dense::map<counter::obj, counter::obj>>(p.copy());
|
||||
replace_map<ankerl::unordered_dense::segmented_map<counter::obj, counter::obj>>(p.copy());
|
||||
replace_map<ankerl::unordered_dense::map<counter::obj,
|
||||
counter::obj,
|
||||
ankerl::unordered_dense::hash<counter::obj>,
|
||||
std::equal_to<counter::obj>,
|
||||
std::deque<std::pair<counter::obj, counter::obj>>>>(p.copy());
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <fuzz/provider.h>
|
||||
#include <fuzz/run.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename Map>
|
||||
void do_string(fuzz::provider p) {
|
||||
auto ank = Map();
|
||||
auto ref = std::unordered_map<std::string, std::string>();
|
||||
|
||||
while (p.has_remaining_bytes()) {
|
||||
auto str = p.string(32);
|
||||
REQUIRE(ank.try_emplace(str, "hello!").second == ref.try_emplace(str, "hello!").second);
|
||||
|
||||
str = p.string(32);
|
||||
auto it_ank = ank.find(str);
|
||||
auto it_ref = ref.find(str);
|
||||
REQUIRE((it_ank == ank.end()) == (it_ref == ref.end()));
|
||||
|
||||
if (it_ank != ank.end()) {
|
||||
ank.erase(it_ank);
|
||||
ref.erase(it_ref);
|
||||
}
|
||||
REQUIRE(ank.size() == ref.size());
|
||||
|
||||
str = p.string(32);
|
||||
REQUIRE(ank.try_emplace(str, "huh").second == ref.try_emplace(str, "huh").second);
|
||||
|
||||
str = p.string(32);
|
||||
REQUIRE(ank.erase(str) == ref.erase(str));
|
||||
}
|
||||
|
||||
REQUIRE(std::unordered_map(ank.begin(), ank.end()) == ref);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("fuzz_string" * doctest::test_suite("fuzz")) {
|
||||
fuzz::run([](fuzz::provider p) {
|
||||
do_string<ankerl::unordered_dense::map<std::string, std::string>>(p.copy());
|
||||
do_string<ankerl::unordered_dense::segmented_map<std::string, std::string>>(p.copy());
|
||||
do_string<ankerl::unordered_dense::map<std::string,
|
||||
std::string,
|
||||
ankerl::unordered_dense::hash<std::string>,
|
||||
std::equal_to<std::string>,
|
||||
std::deque<std::pair<std::string, std::string>>>>(p.copy());
|
||||
});
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <doctest.h> // for ResultBuilder, TestCase, REQUIRE
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <cstdint> // for uint64_t
|
||||
#include <string> // for string, basic_string
|
||||
#include <unordered_set> // for unordered_set
|
||||
|
||||
TEST_CASE("hash_string") {
|
||||
auto h = ankerl::unordered_dense::hash<std::string>();
|
||||
|
||||
auto set = std::unordered_set<uint64_t>();
|
||||
auto str = std::string();
|
||||
for (size_t l = 0; l < 100; ++l) {
|
||||
set.insert(h(str));
|
||||
str.push_back('x');
|
||||
}
|
||||
REQUIRE(set.size() == 100);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#include <doctest.h>
|
||||
|
||||
TEST_CASE("hash_char_types") {
|
||||
// TODO(martinus) make hash generic?
|
||||
// REQUIRE(123 != ankerl::hash<wchar_t>{}(123));
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <doctest.h> // for ResultBuilder, TestCase, REQUIRE
|
||||
|
||||
#include <cstdint> // for uint64_t
|
||||
#include <memory> // for shared_ptr, __unique_ptr_t, make...
|
||||
#include <type_traits> // for declval
|
||||
|
||||
template <typename Ptr>
|
||||
void check(Ptr const& ptr) {
|
||||
REQUIRE(ankerl::unordered_dense::hash<Ptr>{}(ptr) ==
|
||||
ankerl::unordered_dense::hash<decltype(std::declval<Ptr>().get())>{}(ptr.get()));
|
||||
}
|
||||
|
||||
TEST_CASE("hash_smart_ptr") {
|
||||
check(std::unique_ptr<uint64_t>{});
|
||||
check(std::shared_ptr<uint64_t>{});
|
||||
check(std::make_shared<uint64_t>(123U));
|
||||
check(std::make_unique<uint64_t>(123U));
|
||||
check(std::make_unique<uint64_t>(uint64_t{123U}));
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <doctest.h>
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
TEST_CASE("hash_string_view") {
|
||||
auto const* cstr = "The ships hung in the sky in much the same way that bricks don't.";
|
||||
REQUIRE(ankerl::unordered_dense::hash<std::string>{}(std::string{cstr}) ==
|
||||
ankerl::unordered_dense::hash<std::string_view>{}(std::string_view{cstr}));
|
||||
}
|
||||
|
||||
TEST_CASE("unit_hash_u32string") {
|
||||
auto str = std::u32string{};
|
||||
str.push_back(1);
|
||||
str.push_back(2);
|
||||
str.push_back(3);
|
||||
str.push_back(4);
|
||||
str.push_back(5);
|
||||
|
||||
REQUIRE(ankerl::unordered_dense::hash<std::u32string>{}(str) == ankerl::unordered_dense::hash<std::u32string_view>{}(str));
|
||||
REQUIRE(ankerl::unordered_dense::hash<std::u32string>{}(str) != std::hash<std::u32string>{}(str));
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
@@ -0,0 +1,81 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <string> // for string, operator==, allocator
|
||||
#include <string_view> // for basic_string_view, operator""sv
|
||||
#include <utility> // for pair
|
||||
#include <vector> // for vector
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
template <class Map, class First, class Second>
|
||||
auto has(Map const& map, First const& first, Second const& second) -> bool {
|
||||
auto it = map.find(first);
|
||||
return it != map.end() && it->second == second;
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("insert_initializer_list", int, int) {
|
||||
auto m = map_t();
|
||||
m.insert({{1, 2}, {3, 4}, {5, 6}});
|
||||
REQUIRE(m.size() == 3U);
|
||||
REQUIRE(m[1] == 2);
|
||||
REQUIRE(m[3] == 4);
|
||||
REQUIRE(m[5] == 6);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("initializerlist_string", std::string, size_t) {
|
||||
size_t const n1 = 17;
|
||||
size_t const n2 = 10;
|
||||
|
||||
auto m1 = map_t{{"duck", n1}, {"lion", n2}};
|
||||
auto m2 = map_t{{"duck", n1}, {"lion", n2}};
|
||||
|
||||
REQUIRE(m1.size() == 2);
|
||||
REQUIRE(m1["duck"] == n1);
|
||||
REQUIRE(m1["lion"] == n2);
|
||||
|
||||
REQUIRE(m2.size() == 2);
|
||||
auto it = m2.find("duck");
|
||||
REQUIRE((it != m2.end() && it->second == n1));
|
||||
REQUIRE(m2["lion"] == n2);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("insert_initializer_list_string", int, std::string) {
|
||||
auto m = map_t();
|
||||
m.insert({{1, "a"}, {3, "b"}, {5, "c"}});
|
||||
REQUIRE(m.size() == 3U);
|
||||
REQUIRE(m[1] == "a");
|
||||
REQUIRE(m[3] == "b");
|
||||
REQUIRE(m[5] == "c");
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("initializer_list_assign", int, char const*) {
|
||||
auto map = map_t();
|
||||
map[3] = "nope";
|
||||
map = {{1, "a"}, {2, "hello"}, {12346, "world!"}};
|
||||
REQUIRE(map.size() == 3);
|
||||
REQUIRE(has(map, 1, "a"sv));
|
||||
REQUIRE(has(map, 2, "hello"sv));
|
||||
REQUIRE(has(map, 12346, "world!"sv));
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("initializer_list_ctor_alloc", int, char const*) {
|
||||
using alloc_t = std::allocator<std::pair<int, char const*>>;
|
||||
auto map = map_t({{1, "a"}, {2, "hello"}, {12346, "world!"}}, 0, alloc_t{});
|
||||
REQUIRE(map.size() == 3);
|
||||
REQUIRE(has(map, 1, "a"sv));
|
||||
REQUIRE(has(map, 2, "hello"sv));
|
||||
REQUIRE(has(map, 12346, "world!"sv));
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("initializer_list_ctor_hash_alloc", int, char const*) {
|
||||
using hash_t = ankerl::unordered_dense::hash<int>;
|
||||
using alloc_t = std::allocator<std::pair<int, char const*>>;
|
||||
auto map = map_t({{1, "a"}, {2, "hello"}, {12346, "world!"}}, 0, hash_t{}, alloc_t{});
|
||||
REQUIRE(map.size() == 3);
|
||||
REQUIRE(has(map, 1, "a"sv));
|
||||
REQUIRE(has(map, 2, "hello"sv));
|
||||
REQUIRE(has(map, 12346, "world!"sv));
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <tuple> // for forward_as_tuple
|
||||
#include <utility> // for piecewise_construct
|
||||
#include <vector> // for vector
|
||||
|
||||
TEST_CASE_MAP("insert", unsigned int, int) {
|
||||
auto map = map_t();
|
||||
auto const val = typename map_t::value_type(123U, 321);
|
||||
map.insert(val);
|
||||
REQUIRE(map.size() == 1);
|
||||
|
||||
REQUIRE(map[123U] == 321);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("insert_hint", unsigned int, int) {
|
||||
auto map = map_t();
|
||||
auto it = map.insert(map.begin(), {1, 2});
|
||||
|
||||
auto vt = typename decltype(map)::value_type{3, 4};
|
||||
map.insert(it, vt);
|
||||
REQUIRE(map.size() == 2);
|
||||
REQUIRE(map[1] == 2);
|
||||
REQUIRE(map[3] == 4);
|
||||
|
||||
auto const vt2 = typename decltype(map)::value_type{10, 11};
|
||||
map.insert(it, vt2);
|
||||
REQUIRE(map.size() == 3);
|
||||
REQUIRE(map[10] == 11);
|
||||
|
||||
it = map.emplace_hint(it, std::piecewise_construct, std::forward_as_tuple(123), std::forward_as_tuple(321));
|
||||
REQUIRE(map.size() == 4);
|
||||
REQUIRE(map[123] == 321);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <string> // for string, operator==, allocator
|
||||
#include <utility> // for pair
|
||||
#include <vector> // for vector
|
||||
|
||||
TEST_CASE_MAP("insert_or_assign", std::string, std::string) {
|
||||
auto map = map_t();
|
||||
|
||||
auto ret = map.insert_or_assign("a", "b");
|
||||
REQUIRE(ret.second);
|
||||
REQUIRE(ret.first == map.find("a"));
|
||||
REQUIRE(map.size() == 1);
|
||||
REQUIRE(map["a"] == "b");
|
||||
|
||||
ret = map.insert_or_assign("c", "d");
|
||||
REQUIRE(ret.second);
|
||||
REQUIRE(ret.first == map.find("c"));
|
||||
REQUIRE(map.size() == 2);
|
||||
REQUIRE(map["c"] == "d");
|
||||
|
||||
ret = map.insert_or_assign("c", "dd");
|
||||
REQUIRE_FALSE(ret.second);
|
||||
REQUIRE(ret.first == map.find("c"));
|
||||
REQUIRE(map.size() == 2);
|
||||
REQUIRE(map["c"] == "dd");
|
||||
|
||||
std::string key = "a";
|
||||
std::string value = "bb";
|
||||
ret = map.insert_or_assign(key, value);
|
||||
REQUIRE_FALSE(ret.second);
|
||||
REQUIRE(ret.first == map.find("a"));
|
||||
REQUIRE(map.size() == 2);
|
||||
REQUIRE(map["a"] == "bb");
|
||||
|
||||
key = "e";
|
||||
value = "f";
|
||||
auto pos = map.insert_or_assign(map.end(), key, value);
|
||||
REQUIRE(pos == map.find("e"));
|
||||
REQUIRE(map.size() == 3);
|
||||
REQUIRE(map["e"] == "f");
|
||||
|
||||
pos = map.insert_or_assign(map.begin(), "e", "ff");
|
||||
REQUIRE(pos == map.find("e"));
|
||||
REQUIRE(map.size() == 3);
|
||||
REQUIRE(map["e"] == "ff");
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <cstdint> // for uint64_t
|
||||
#include <vector> // for vector
|
||||
|
||||
TEST_CASE_MAP("iterators_empty", size_t, size_t) {
|
||||
for (size_t i = 0; i < 10; ++i) {
|
||||
auto m = ankerl::unordered_dense::map<size_t, size_t>();
|
||||
REQUIRE(m.begin() == m.end());
|
||||
|
||||
REQUIRE(m.end() == m.find(132));
|
||||
|
||||
m[1];
|
||||
REQUIRE(m.begin() != m.end());
|
||||
REQUIRE(++m.begin() == m.end());
|
||||
m.clear();
|
||||
|
||||
REQUIRE(m.begin() == m.end());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/counter.h>
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <cstdint> // for uint64_t
|
||||
#include <unordered_set> // for unordered_set
|
||||
#include <utility> // for pair
|
||||
#include <vector> // for vector
|
||||
|
||||
TEST_CASE_MAP("iterators_erase", counter::obj, counter::obj) {
|
||||
auto counts = counter();
|
||||
INFO(counts);
|
||||
{
|
||||
counts("begin");
|
||||
auto map = map_t();
|
||||
for (size_t i = 0; i < 100; ++i) {
|
||||
map[counter::obj(i * 101, counts)] = counter::obj(i * 101, counts);
|
||||
}
|
||||
|
||||
auto it = map.find(counter::obj(size_t{20} * 101, counts));
|
||||
REQUIRE(map.size() == 100);
|
||||
REQUIRE(map.end() != map.find(counter::obj(size_t{20} * 101, counts)));
|
||||
it = map.erase(it);
|
||||
REQUIRE(map.size() == 99);
|
||||
REQUIRE(map.end() == map.find(counter::obj(size_t{20} * 101, counts)));
|
||||
|
||||
it = map.begin();
|
||||
size_t current_size = map.size();
|
||||
std::unordered_set<uint64_t> keys;
|
||||
while (it != map.end()) {
|
||||
REQUIRE(keys.emplace(it->first.get()).second);
|
||||
it = map.erase(it);
|
||||
current_size--;
|
||||
REQUIRE(map.size() == current_size);
|
||||
}
|
||||
REQUIRE(map.size() == static_cast<size_t>(0));
|
||||
counts("done");
|
||||
}
|
||||
counts("destructed");
|
||||
REQUIRE(counts.dtor() ==
|
||||
counts.ctor() + counts.static_default_ctor + counts.copy_ctor() + counts.default_ctor() + counts.move_ctor());
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <algorithm> // for max
|
||||
#include <utility> // for pair
|
||||
#include <vector> // for vector
|
||||
|
||||
TEST_CASE_MAP("iterators_insert", int, int) {
|
||||
auto v = std::vector<std::pair<int, int>>();
|
||||
v.reserve(1000);
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
v.emplace_back(i, i);
|
||||
}
|
||||
|
||||
auto map = map_t(v.begin(), v.end());
|
||||
REQUIRE(map.size() == v.size());
|
||||
for (auto const& kv : v) {
|
||||
REQUIRE(map.count(kv.first) == 1);
|
||||
auto it = map.find(kv.first);
|
||||
REQUIRE(it != map.end());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
TEST_CASE_MAP("load_factor", int, int) {
|
||||
auto m = map_t();
|
||||
|
||||
REQUIRE(static_cast<double>(m.load_factor()) == doctest::Approx(0.0));
|
||||
|
||||
for (int i = 0; i < 10000; ++i) {
|
||||
m.emplace(i, i);
|
||||
REQUIRE(m.load_factor() > 0.0F);
|
||||
REQUIRE(m.load_factor() <= 0.8F);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/checksum.h>
|
||||
#include <app/counter.h>
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <third-party/nanobench.h>
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <cstdint> // for uint64_t
|
||||
#include <utility> // for move
|
||||
|
||||
template <typename Map>
|
||||
void test() {
|
||||
auto counts = counter();
|
||||
INFO(counts);
|
||||
|
||||
auto rng = ankerl::nanobench::Rng();
|
||||
for (size_t trial = 0; trial < 4; ++trial) {
|
||||
{
|
||||
counts("start");
|
||||
auto maps = Map();
|
||||
for (size_t i = 0; i < 100; ++i) {
|
||||
auto a = rng.bounded(20);
|
||||
auto b = rng.bounded(20);
|
||||
auto x = static_cast<size_t>(rng());
|
||||
// std::cout << i << ": map[" << a << "][" << b << "] = " << x << std::endl;
|
||||
maps[counter::obj(a, counts)][counter::obj(b, counts)] = counter::obj(x, counts);
|
||||
}
|
||||
counts("filled");
|
||||
|
||||
Map maps_copied;
|
||||
maps_copied = maps;
|
||||
REQUIRE(checksum::mapmap(maps_copied) == checksum::mapmap(maps));
|
||||
REQUIRE(maps_copied == maps);
|
||||
counts("copied");
|
||||
|
||||
Map maps_moved;
|
||||
maps_moved = std::move(maps_copied);
|
||||
counts("moved");
|
||||
|
||||
// move
|
||||
REQUIRE(checksum::mapmap(maps_moved) == checksum::mapmap(maps));
|
||||
REQUIRE(maps_copied.size() == 0); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved)
|
||||
maps_copied = std::move(maps_moved);
|
||||
counts("moved back");
|
||||
|
||||
// move back
|
||||
REQUIRE(checksum::mapmap(maps_copied) == checksum::mapmap(maps));
|
||||
REQUIRE(maps_moved.size() == 0); // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved)
|
||||
counts("done");
|
||||
}
|
||||
counts("all destructed");
|
||||
REQUIRE(counts.dtor() ==
|
||||
counts.ctor() + counts.static_default_ctor + counts.copy_ctor() + counts.default_ctor() + counts.move_ctor());
|
||||
}
|
||||
}
|
||||
|
||||
TYPE_TO_STRING_MAP(counter::obj, ankerl::unordered_dense::map<counter::obj, counter::obj>);
|
||||
TYPE_TO_STRING_MAP(counter::obj, ankerl::unordered_dense::segmented_map<counter::obj, counter::obj>);
|
||||
|
||||
TEST_CASE_MAP("mapmap_map", counter::obj, ankerl::unordered_dense::map<counter::obj, counter::obj>) {
|
||||
test<map_t>();
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("mapmap_segmented_map", counter::obj, ankerl::unordered_dense::segmented_map<counter::obj, counter::obj>) {
|
||||
test<map_t>();
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <cstdint> // for uint32_t
|
||||
#include <functional> // for equal_to
|
||||
#include <limits> // for numeric_limits
|
||||
|
||||
TEST_CASE_MAP("max_load_factor", int, int) {
|
||||
auto map_60 = map_t();
|
||||
auto map_90 = map_t();
|
||||
REQUIRE(map_60.max_load_factor() == doctest::Approx(0.8));
|
||||
map_60.max_load_factor(0.6F);
|
||||
map_90.max_load_factor(0.9F);
|
||||
REQUIRE(map_60.max_load_factor() == doctest::Approx(0.6));
|
||||
REQUIRE(map_90.max_load_factor() == doctest::Approx(0.9));
|
||||
|
||||
map_60[0];
|
||||
map_90[0];
|
||||
REQUIRE(map_60.bucket_count() == map_90.bucket_count());
|
||||
|
||||
auto const old_bucket_count = map_60.bucket_count();
|
||||
|
||||
// fill up maps until bucket_count increases
|
||||
int i = 0;
|
||||
while (map_60.bucket_count() == old_bucket_count) {
|
||||
map_60[++i];
|
||||
}
|
||||
while (map_90.bucket_count() == old_bucket_count) {
|
||||
map_90[++i];
|
||||
}
|
||||
|
||||
// 0.9 max_load should fit more than map_60
|
||||
REQUIRE(map_90.size() > map_60.size());
|
||||
|
||||
map_60.max_load_factor(0.9F);
|
||||
REQUIRE(map_60.max_load_factor() == map_90.max_load_factor());
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("key_eq", int, int) {
|
||||
auto const map = map_t();
|
||||
auto eq = map.key_eq();
|
||||
REQUIRE(eq(5, 5));
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#define ENABLE_LOG_LINE
|
||||
#include <app/doctest.h>
|
||||
#include <app/print.h>
|
||||
|
||||
#include <type_traits> // for remove_reference, remove_referen...
|
||||
#include <utility> // for move
|
||||
|
||||
TEST_CASE_MAP("move_to_moved", int, int) {
|
||||
auto a = map_t();
|
||||
a[1] = 2;
|
||||
auto moved = std::move(a);
|
||||
|
||||
auto c = map_t();
|
||||
c[3] = 4;
|
||||
|
||||
// assign to a moved map
|
||||
a = std::move(c);
|
||||
|
||||
a[5] = 6;
|
||||
moved[6] = 7;
|
||||
REQUIRE(moved[6] == 7);
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/counter.h>
|
||||
#include <app/doctest.h>
|
||||
#include <third-party/nanobench.h>
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <cstdint> // for uint64_t
|
||||
#include <type_traits> // for __enable_if_t
|
||||
#include <unordered_map> // for _Node_iterator, unordered_map
|
||||
#include <utility> // for pair, make_pair
|
||||
#include <vector> // for vector
|
||||
|
||||
TEST_CASE_MAP("multiple_different_APIs" * doctest::test_suite("stochastic"), counter::obj, counter::obj) {
|
||||
counter counts;
|
||||
INFO(counts);
|
||||
|
||||
map_t map;
|
||||
REQUIRE(map.size() == static_cast<size_t>(0));
|
||||
std::pair<typename map_t::iterator, bool> it_outer =
|
||||
map.insert(typename map_t::value_type{{32145, counts}, {123, counts}});
|
||||
REQUIRE(it_outer.second);
|
||||
REQUIRE(it_outer.first->first.get() == 32145);
|
||||
REQUIRE(it_outer.first->second.get() == 123);
|
||||
REQUIRE(map.size() == 1);
|
||||
|
||||
const size_t times = 10000;
|
||||
for (size_t i = 0; i < times; ++i) {
|
||||
INFO(i);
|
||||
std::pair<typename map_t::iterator, bool> it_inner =
|
||||
map.insert(typename map_t::value_type({i * 4U, counts}, {i, counts}));
|
||||
|
||||
REQUIRE(it_inner.second);
|
||||
REQUIRE(it_inner.first->first.get() == i * 4);
|
||||
REQUIRE(it_inner.first->second.get() == i);
|
||||
|
||||
auto found = map.find(counter::obj{i * 4, counts});
|
||||
REQUIRE(map.end() != found);
|
||||
REQUIRE(found->second.get() == i);
|
||||
REQUIRE(map.size() == 2 + i);
|
||||
}
|
||||
|
||||
// check if everything can be found
|
||||
for (size_t i = 0; i < times; ++i) {
|
||||
auto found = map.find(counter::obj{i * 4, counts});
|
||||
REQUIRE(map.end() != found);
|
||||
REQUIRE(found->second.get() == i);
|
||||
REQUIRE(found->first.get() == i * 4);
|
||||
}
|
||||
|
||||
// check non-elements
|
||||
for (size_t i = 0; i < times; ++i) {
|
||||
auto found = map.find(counter::obj{(i + times) * 4U, counts});
|
||||
REQUIRE(map.end() == found);
|
||||
}
|
||||
|
||||
// random test against std::unordered_map
|
||||
map.clear();
|
||||
std::unordered_map<uint64_t, uint64_t> uo;
|
||||
|
||||
auto rng = ankerl::nanobench::Rng();
|
||||
INFO("seed=" << rng.state());
|
||||
|
||||
for (uint64_t i = 0; i < times; ++i) {
|
||||
auto r = static_cast<size_t>(rng.bounded(times / 4));
|
||||
auto rhh_it = map.insert(typename map_t::value_type({r, counts}, {r * 2, counts}));
|
||||
auto uo_it = uo.insert(std::make_pair(r, r * 2));
|
||||
REQUIRE(rhh_it.second == uo_it.second);
|
||||
REQUIRE(rhh_it.first->first.get() == uo_it.first->first);
|
||||
REQUIRE(rhh_it.first->second.get() == uo_it.first->second);
|
||||
REQUIRE(map.size() == uo.size());
|
||||
|
||||
r = rng.bounded(times / 4);
|
||||
auto map_it = map.find(counter::obj{r, counts});
|
||||
auto uo_it2 = uo.find(r);
|
||||
REQUIRE((map.end() == map_it) == (uo.end() == uo_it2));
|
||||
if (map.end() != map_it) {
|
||||
REQUIRE(map_it->first.get() == uo_it2->first);
|
||||
REQUIRE(map_it->second.get() == uo_it2->second);
|
||||
}
|
||||
}
|
||||
|
||||
uo.clear();
|
||||
map.clear();
|
||||
for (size_t i = 0; i < times; ++i) {
|
||||
const auto r = static_cast<size_t>(rng.bounded(times / 4));
|
||||
map[{r, counts}] = {r * 2, counts};
|
||||
uo[r] = r * 2;
|
||||
REQUIRE(map.find(counter::obj{r, counts})->second.get() == uo.find(r)->second);
|
||||
REQUIRE(map.size() == uo.size());
|
||||
}
|
||||
|
||||
std::size_t num_checks = 0;
|
||||
for (auto it = map.begin(); it != map.end(); ++it) {
|
||||
REQUIRE(uo.end() != uo.find(it->first.get()));
|
||||
++num_checks;
|
||||
}
|
||||
REQUIRE(map.size() == num_checks);
|
||||
|
||||
num_checks = 0;
|
||||
map_t const& const_rhhs = map;
|
||||
for (const typename map_t::value_type& vt : const_rhhs) {
|
||||
REQUIRE(uo.end() != uo.find(vt.first.get()));
|
||||
++num_checks;
|
||||
}
|
||||
REQUIRE(map.size() == num_checks);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <doctest.h>
|
||||
|
||||
namespace versioned_namespace = ankerl::unordered_dense::v4_4_0;
|
||||
|
||||
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>>);
|
||||
|
||||
TEST_CASE("version_namespace") {
|
||||
auto map = versioned_namespace::map<int, int>{};
|
||||
REQUIRE(map.empty());
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <tuple> // for forward_as_tuple
|
||||
#include <utility> // for move, piecewise_construct
|
||||
|
||||
// not copyable, but movable.
|
||||
class no_copy {
|
||||
public:
|
||||
no_copy() noexcept = default;
|
||||
explicit no_copy(size_t d) noexcept
|
||||
: m_mData(d) {}
|
||||
|
||||
~no_copy() = default;
|
||||
no_copy(no_copy const&) = delete;
|
||||
auto operator=(no_copy const&) -> no_copy& = delete;
|
||||
|
||||
no_copy(no_copy&&) = default;
|
||||
auto operator=(no_copy&&) -> no_copy& = default;
|
||||
|
||||
[[nodiscard]] auto data() const -> size_t {
|
||||
return m_mData;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t m_mData{};
|
||||
};
|
||||
|
||||
TYPE_TO_STRING_MAP(size_t, no_copy);
|
||||
|
||||
TEST_CASE_MAP("not_copyable", size_t, no_copy) {
|
||||
// it's ok because it is movable.
|
||||
map_t m;
|
||||
for (size_t i = 0; i < 100; ++i) {
|
||||
m[i];
|
||||
m.emplace(std::piecewise_construct, std::forward_as_tuple(i * 100), std::forward_as_tuple(i));
|
||||
}
|
||||
REQUIRE(m.size() == 199);
|
||||
|
||||
// not copyable, because m is not copyable!
|
||||
// map_t m2 = m;
|
||||
|
||||
// movable works
|
||||
map_t m2 = std::move(m);
|
||||
REQUIRE(m2.size() == 199);
|
||||
m = map_t{};
|
||||
REQUIRE(m.size() == 0);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <tuple> // for forward_as_tuple
|
||||
#include <utility> // for piecewise_construct
|
||||
|
||||
class no_move {
|
||||
public:
|
||||
no_move() noexcept = default;
|
||||
explicit no_move(size_t d) noexcept
|
||||
: m_mData(d) {}
|
||||
~no_move() = default;
|
||||
|
||||
no_move(no_move const&) = default;
|
||||
auto operator=(no_move const&) -> no_move& = default;
|
||||
|
||||
no_move(no_move&&) = delete;
|
||||
auto operator=(no_move&&) -> no_move& = delete;
|
||||
|
||||
[[nodiscard]] auto data() const -> size_t {
|
||||
return m_mData;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t m_mData{};
|
||||
};
|
||||
|
||||
TYPE_TO_STRING_MAP(size_t, no_move);
|
||||
|
||||
// doesn't work with robin_hood::unordered_flat_map<size_t, NoMove> because not movable and not
|
||||
// copyable
|
||||
TEST_CASE_MAP("not_moveable", size_t, no_move) {
|
||||
// it's ok because it is movable.
|
||||
auto m = map_t();
|
||||
for (size_t i = 0; i < 100; ++i) {
|
||||
m[i];
|
||||
m.emplace(std::piecewise_construct, std::forward_as_tuple(i * 100), std::forward_as_tuple(i));
|
||||
}
|
||||
REQUIRE(m.size() == 199);
|
||||
|
||||
// not copyable, because m is not copyable!
|
||||
// map_t m2 = m;
|
||||
|
||||
// not movable
|
||||
// map_t m2 = std::move(m);
|
||||
// REQUIRE(m2.size() == 199);
|
||||
m.clear();
|
||||
REQUIRE(m.size() == 0);
|
||||
}
|
||||
+232
@@ -0,0 +1,232 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <cstdint> // for uint64_t
|
||||
#include <stdexcept> // thrown in no_null_memory_resource
|
||||
#include <string_view> // for string_view
|
||||
#include <utility> // for move
|
||||
#include <vector> // for vector
|
||||
|
||||
#if defined(ANKERL_UNORDERED_DENSE_PMR)
|
||||
|
||||
// windows' vector has different allocation behavior, macos has linker errors
|
||||
# if __linux__
|
||||
|
||||
class logging_memory_resource : public ANKERL_UNORDERED_DENSE_PMR::memory_resource {
|
||||
auto do_allocate(std::size_t bytes, std::size_t alignment) -> void* override {
|
||||
fmt::print("+ {} bytes, {} alignment\n", bytes, alignment);
|
||||
return ANKERL_UNORDERED_DENSE_PMR::new_delete_resource()->allocate(bytes, alignment);
|
||||
}
|
||||
|
||||
void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override {
|
||||
fmt::print("- {} bytes, {} alignment, {} ptr\n", bytes, alignment, p);
|
||||
return ANKERL_UNORDERED_DENSE_PMR::new_delete_resource()->deallocate(p, bytes, alignment);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto do_is_equal(const ANKERL_UNORDERED_DENSE_PMR::memory_resource& other) const noexcept -> bool override {
|
||||
return this == &other;
|
||||
}
|
||||
};
|
||||
|
||||
class no_null_memory_resource : public ANKERL_UNORDERED_DENSE_PMR::memory_resource {
|
||||
auto do_allocate(std::size_t bytes, std::size_t alignment) -> void* override {
|
||||
if (bytes == 0) {
|
||||
throw std::runtime_error("no_null_memory_resource::do_allocate should not do_allocate 0");
|
||||
}
|
||||
return ANKERL_UNORDERED_DENSE_PMR::new_delete_resource()->allocate(bytes, alignment);
|
||||
}
|
||||
|
||||
void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override {
|
||||
if (nullptr == p || 0U == bytes || 0U == alignment) {
|
||||
throw std::runtime_error("no_null_memory_resource::do_deallocate should not deallocate with any 0 value");
|
||||
}
|
||||
return ANKERL_UNORDERED_DENSE_PMR::new_delete_resource()->deallocate(p, bytes, alignment);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto do_is_equal(const ANKERL_UNORDERED_DENSE_PMR::memory_resource& other) const noexcept -> bool override {
|
||||
return this == &other;
|
||||
}
|
||||
};
|
||||
|
||||
class track_peak_memory_resource : public ANKERL_UNORDERED_DENSE_PMR::memory_resource {
|
||||
uint64_t m_peak = 0;
|
||||
uint64_t m_current = 0;
|
||||
uint64_t m_num_allocs = 0;
|
||||
uint64_t m_num_deallocs = 0;
|
||||
mutable uint64_t m_num_is_equals = 0;
|
||||
|
||||
auto do_allocate(std::size_t bytes, std::size_t alignment) -> void* override {
|
||||
++m_num_allocs;
|
||||
m_current += bytes;
|
||||
if (m_current > m_peak) {
|
||||
m_peak = m_current;
|
||||
}
|
||||
return ANKERL_UNORDERED_DENSE_PMR::new_delete_resource()->allocate(bytes, alignment);
|
||||
}
|
||||
|
||||
void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override {
|
||||
if (p == nullptr) {
|
||||
return;
|
||||
}
|
||||
++m_num_deallocs;
|
||||
m_current -= bytes;
|
||||
return ANKERL_UNORDERED_DENSE_PMR::new_delete_resource()->deallocate(p, bytes, alignment);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto do_is_equal(const ANKERL_UNORDERED_DENSE_PMR::memory_resource& other) const noexcept -> bool override {
|
||||
++m_num_is_equals;
|
||||
return this == &other;
|
||||
}
|
||||
|
||||
public:
|
||||
[[nodiscard]] auto current() const -> uint64_t {
|
||||
return m_current;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto peak() const -> uint64_t {
|
||||
return m_peak;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto num_allocs() const -> uint64_t {
|
||||
return m_num_allocs;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto num_deallocs() const -> uint64_t {
|
||||
return m_num_deallocs;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto num_is_equals() const -> uint64_t {
|
||||
return m_num_is_equals;
|
||||
}
|
||||
};
|
||||
|
||||
TYPE_TO_STRING_PMR_MAP(uint64_t, uint64_t);
|
||||
|
||||
TEST_CASE_PMR_MAP("pmr", uint64_t, uint64_t) {
|
||||
auto mr = track_peak_memory_resource();
|
||||
{
|
||||
REQUIRE(mr.current() == 0);
|
||||
auto map = map_t(&mr);
|
||||
|
||||
for (size_t i = 0; i < 1; ++i) {
|
||||
map[i] = i;
|
||||
}
|
||||
REQUIRE(mr.current() != 0);
|
||||
|
||||
// gets a copy, but it has the same memory resource
|
||||
auto alloc = map.get_allocator();
|
||||
REQUIRE(alloc.resource() == &mr);
|
||||
}
|
||||
REQUIRE(mr.current() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE_PMR_MAP("pmr_no_null", uint64_t, uint64_t) {
|
||||
auto mr = no_null_memory_resource();
|
||||
{ auto map = map_t(&mr); }
|
||||
{
|
||||
auto map = map_t(&mr);
|
||||
for (size_t i = 0; i < 1; ++i) {
|
||||
map[i] = i;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto map = map_t(&mr);
|
||||
for (size_t i = 0; i < 1; ++i) {
|
||||
map[i] = i;
|
||||
}
|
||||
auto map2 = std::move(map);
|
||||
}
|
||||
}
|
||||
|
||||
void show([[maybe_unused]] track_peak_memory_resource const& mr, [[maybe_unused]] std::string_view name) {
|
||||
// fmt::print("{}: {} allocs, {} deallocs, {} is_equals\n", name, mr.num_allocs(), mr.num_deallocs(), mr.num_is_equals());
|
||||
}
|
||||
|
||||
// the following tests are vector specific, so don't use segmented_vector
|
||||
|
||||
TEST_CASE("pmr_copy") {
|
||||
using map_t = ankerl::unordered_dense::pmr::map<uint64_t, uint64_t>;
|
||||
auto mr1 = track_peak_memory_resource();
|
||||
auto map1 = map_t(&mr1);
|
||||
map1[1] = 2;
|
||||
|
||||
auto mr2 = track_peak_memory_resource();
|
||||
auto map2 = map_t(&mr2);
|
||||
map2[3] = 4;
|
||||
show(mr1, "mr1");
|
||||
show(mr2, "mr2");
|
||||
|
||||
map1 = map2;
|
||||
REQUIRE(map1.size() == 1);
|
||||
REQUIRE(map1.find(3) != map1.end());
|
||||
show(mr1, "mr1");
|
||||
show(mr2, "mr2");
|
||||
|
||||
REQUIRE(mr1.num_allocs() == 3);
|
||||
REQUIRE(mr1.num_deallocs() == 1);
|
||||
REQUIRE(mr1.num_is_equals() == 0);
|
||||
|
||||
REQUIRE(mr2.num_allocs() == 2);
|
||||
REQUIRE(mr2.num_deallocs() == 0);
|
||||
REQUIRE(mr2.num_is_equals() == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("pmr_move_different_mr") {
|
||||
using map_t = ankerl::unordered_dense::pmr::map<uint64_t, uint64_t>;
|
||||
|
||||
auto mr1 = track_peak_memory_resource();
|
||||
auto map1 = map_t(&mr1);
|
||||
map1[1] = 2;
|
||||
|
||||
auto mr2 = track_peak_memory_resource();
|
||||
auto map2 = map_t(&mr2);
|
||||
map2[3] = 4;
|
||||
show(mr1, "mr1");
|
||||
show(mr2, "mr2");
|
||||
|
||||
map1 = std::move(map2);
|
||||
REQUIRE(map1.size() == 1);
|
||||
REQUIRE(map1.find(3) != map1.end());
|
||||
show(mr1, "mr1");
|
||||
show(mr2, "mr2");
|
||||
|
||||
REQUIRE(mr1.num_allocs() == 3);
|
||||
REQUIRE(mr1.num_deallocs() == 1);
|
||||
REQUIRE(mr1.num_is_equals() == 1);
|
||||
|
||||
REQUIRE(mr2.num_allocs() == 2);
|
||||
REQUIRE(mr2.num_deallocs() == 0);
|
||||
REQUIRE(mr2.num_is_equals() == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("pmr_move_same_mr") {
|
||||
using map_t = ankerl::unordered_dense::pmr::map<uint64_t, uint64_t>;
|
||||
|
||||
auto mr1 = track_peak_memory_resource();
|
||||
auto map1 = map_t(&mr1);
|
||||
map1[1] = 2;
|
||||
REQUIRE(mr1.num_allocs() == 2);
|
||||
REQUIRE(mr1.num_deallocs() == 0);
|
||||
REQUIRE(mr1.num_is_equals() == 0);
|
||||
|
||||
auto map2 = ankerl::unordered_dense::pmr::map<uint64_t, uint64_t>(&mr1);
|
||||
map2[3] = 4;
|
||||
show(mr1, "mr1");
|
||||
|
||||
map1 = std::move(map2);
|
||||
REQUIRE(map1.size() == 1);
|
||||
REQUIRE(map1.find(3) != map1.end());
|
||||
show(mr1, "mr1");
|
||||
|
||||
REQUIRE(mr1.num_allocs() == 5); // 5 because of the initial allocation
|
||||
REQUIRE(mr1.num_deallocs() == 2);
|
||||
REQUIRE(mr1.num_is_equals() == 0);
|
||||
}
|
||||
|
||||
# endif
|
||||
#endif
|
||||
@@ -0,0 +1,108 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#define ENABLE_LOG_LINE
|
||||
#include <app/doctest.h>
|
||||
#include <app/print.h>
|
||||
|
||||
#if defined(ANKERL_UNORDERED_DENSE_PMR)
|
||||
// windows' vector has different allocation behavior, macos has linker errors
|
||||
# if __linux__
|
||||
|
||||
namespace {
|
||||
|
||||
// creates a map and moves it out
|
||||
template <typename Map>
|
||||
auto return_hello_world(ANKERL_UNORDERED_DENSE_PMR::memory_resource* resource) -> Map {
|
||||
Map map_default_resource(resource);
|
||||
map_default_resource[0] = "Hello";
|
||||
map_default_resource[1] = "World";
|
||||
return map_default_resource;
|
||||
}
|
||||
|
||||
template <typename Map>
|
||||
auto doTest() {
|
||||
ANKERL_UNORDERED_DENSE_PMR::synchronized_pool_resource pool;
|
||||
|
||||
{
|
||||
Map m(ANKERL_UNORDERED_DENSE_PMR::new_delete_resource());
|
||||
m = return_hello_world<Map>(&pool);
|
||||
REQUIRE(m.contains(0));
|
||||
}
|
||||
|
||||
{
|
||||
Map m(ANKERL_UNORDERED_DENSE_PMR::new_delete_resource());
|
||||
m[0] = "foo";
|
||||
m = return_hello_world<Map>(&pool);
|
||||
REQUIRE(m[0] == "Hello");
|
||||
}
|
||||
|
||||
{
|
||||
Map m(return_hello_world<Map>(&pool), ANKERL_UNORDERED_DENSE_PMR::new_delete_resource());
|
||||
REQUIRE(m.contains(0));
|
||||
}
|
||||
|
||||
{
|
||||
Map a(ANKERL_UNORDERED_DENSE_PMR::new_delete_resource());
|
||||
a[0] = "hello";
|
||||
Map b(&pool);
|
||||
b[0] = "world";
|
||||
|
||||
// looping here causes lots of memory held up
|
||||
// in the resources
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
std::swap(a, b);
|
||||
REQUIRE(b[0] == "hello");
|
||||
REQUIRE(a[0] == "world");
|
||||
|
||||
std::swap(a, b);
|
||||
REQUIRE(a[0] == "hello");
|
||||
REQUIRE(b[0] == "world");
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
Map a(ANKERL_UNORDERED_DENSE_PMR::new_delete_resource());
|
||||
a[0] = "hello";
|
||||
Map b(&pool);
|
||||
b[0] = "world";
|
||||
|
||||
// looping here causes lots of memory held up
|
||||
// in the resources
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
std::swap(a, b);
|
||||
REQUIRE(b[0] == "hello");
|
||||
REQUIRE(a[0] == "world");
|
||||
|
||||
std::swap(a, b);
|
||||
REQUIRE(a[0] == "hello");
|
||||
REQUIRE(b[0] == "world");
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
Map a(&pool);
|
||||
a[0] = "world";
|
||||
|
||||
Map tmp(ANKERL_UNORDERED_DENSE_PMR::new_delete_resource());
|
||||
tmp[0] = "nope";
|
||||
tmp = std::move(a);
|
||||
REQUIRE(tmp[0] == "world");
|
||||
REQUIRE(a.empty());
|
||||
a[0] = "hey";
|
||||
REQUIRE(a.size() == 1);
|
||||
REQUIRE(a[0] == "hey");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("move_with_allocators") {
|
||||
doTest<ankerl::unordered_dense::pmr::map<int, std::string>>();
|
||||
}
|
||||
|
||||
TEST_CASE("move_with_allocators_segmented") {
|
||||
doTest<ankerl::unordered_dense::pmr::segmented_map<int, std::string>>();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
# endif
|
||||
#endif
|
||||
@@ -0,0 +1,24 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
|
||||
TEST_CASE_MAP("rehash", size_t, int) {
|
||||
auto map = map_t();
|
||||
|
||||
for (size_t i = 0; i < 1000; ++i) {
|
||||
map[i];
|
||||
}
|
||||
auto old_bucket_size = map.bucket_count();
|
||||
|
||||
map.rehash(10000);
|
||||
REQUIRE(map.bucket_count() >= 10000);
|
||||
map.rehash(0);
|
||||
REQUIRE(map.bucket_count() == old_bucket_size);
|
||||
|
||||
map.clear();
|
||||
map.rehash(0);
|
||||
REQUIRE(map.bucket_count() > 0);
|
||||
REQUIRE(map.bucket_count() < old_bucket_size);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/counter.h>
|
||||
#include <app/doctest.h>
|
||||
|
||||
TEST_CASE_MAP("replace", counter::obj, counter::obj) {
|
||||
auto counts = counter{};
|
||||
INFO(counts);
|
||||
|
||||
auto container = typename map_t::value_container_type{};
|
||||
|
||||
for (size_t i = 0; i < 100; ++i) {
|
||||
container.emplace_back(counter::obj{i, counts}, counter::obj{i, counts});
|
||||
container.emplace_back(counter::obj{i, counts}, counter::obj{i, counts});
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 10; ++i) {
|
||||
container.emplace_back(counter::obj{i, counts}, counter::obj{i, counts});
|
||||
}
|
||||
|
||||
// add some elements
|
||||
auto map = map_t();
|
||||
for (size_t i = 0; i < 10; ++i) {
|
||||
map.try_emplace(counter::obj{i, counts}, counter::obj{i, counts});
|
||||
}
|
||||
|
||||
map.replace(std::move(container));
|
||||
|
||||
REQUIRE(map.size() == 100U);
|
||||
for (size_t i = 0; i < 100; ++i) {
|
||||
REQUIRE(map.contains(counter::obj{i, counts}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
TEST_CASE_MAP("reserve", int, int) {
|
||||
// there's no capacity() for std::deque
|
||||
if constexpr (!std::is_same_v<typename map_t::value_container_type, std::deque<std::pair<int, int>>>) {
|
||||
auto map = map_t();
|
||||
REQUIRE(map.values().capacity() <= 1000U);
|
||||
map.reserve(1000);
|
||||
REQUIRE(map.values().capacity() >= 1000U);
|
||||
REQUIRE(0U == map.values().size());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/checksum.h>
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <cstdint> // for uint64_t
|
||||
#include <string> // for allocator, string
|
||||
#include <vector> // for vector
|
||||
|
||||
TEST_CASE_MAP("reserve_and_assign", std::string, uint64_t) {
|
||||
map_t a = {
|
||||
{"button", {}},
|
||||
{"selectbox-tr", {}},
|
||||
{"slidertrack-t", {}},
|
||||
{"sliderarrowinc-hover", {}},
|
||||
{"text-l", {}},
|
||||
{"title-bar-l", {}},
|
||||
{"checkbox-checked-hover", {}},
|
||||
{"datagridexpand-active", {}},
|
||||
{"icon-waves", {}},
|
||||
{"sliderarrowdec-hover", {}},
|
||||
{"datagridexpand-collapsed", {}},
|
||||
{"sliderarrowinc-active", {}},
|
||||
{"radio-active", {}},
|
||||
{"radio-checked", {}},
|
||||
{"selectvalue-hover", {}},
|
||||
{"huditem-l", {}},
|
||||
{"datagridexpand-collapsed-active", {}},
|
||||
{"slidertrack-b", {}},
|
||||
{"selectarrow-hover", {}},
|
||||
{"window-r", {}},
|
||||
{"selectbox-tl", {}},
|
||||
{"icon-score", {}},
|
||||
{"datagridheader-r", {}},
|
||||
{"icon-game", {}},
|
||||
{"sliderbar-c", {}},
|
||||
{"window-c", {}},
|
||||
{"datagridexpand-hover", {}},
|
||||
{"button-hover", {}},
|
||||
{"icon-hiscore", {}},
|
||||
{"sliderbar-hover-t", {}},
|
||||
{"sliderbar-hover-c", {}},
|
||||
{"selectarrow-active", {}},
|
||||
{"window-tl", {}},
|
||||
{"checkbox-active", {}},
|
||||
{"sliderarrowdec-active", {}},
|
||||
{"sliderbar-active-b", {}},
|
||||
{"sliderarrowdec", {}},
|
||||
{"window-bl", {}},
|
||||
{"datagridheader-l", {}},
|
||||
{"sliderbar-t", {}},
|
||||
{"sliderbar-active-t", {}},
|
||||
{"text-c", {}},
|
||||
{"window-br", {}},
|
||||
{"huditem-c", {}},
|
||||
{"selectbox-l", {}},
|
||||
{"icon-flag", {}},
|
||||
{"sliderbar-hover-b", {}},
|
||||
{"icon-help", {}},
|
||||
{"selectvalue", {}},
|
||||
{"title-bar-r", {}},
|
||||
{"sliderbar-active-c", {}},
|
||||
{"huditem-r", {}},
|
||||
{"radio-checked-active", {}},
|
||||
{"selectbox-c", {}},
|
||||
{"selectbox-bl", {}},
|
||||
{"icon-invader", {}},
|
||||
{"checkbox-checked-active", {}},
|
||||
{"slidertrack-c", {}},
|
||||
{"sliderarrowinc", {}},
|
||||
{"checkbox", {}},
|
||||
};
|
||||
|
||||
map_t b;
|
||||
b = a;
|
||||
REQUIRE(b.find("button") != b.end()); // Passes OK.
|
||||
|
||||
map_t c;
|
||||
c.reserve(51);
|
||||
c = a;
|
||||
REQUIRE(checksum::map(a) == checksum::map(c));
|
||||
REQUIRE(c.find("button") != c.end()); // Fails.
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("unit_reserve_only_flat", std::string, uint64_t) {
|
||||
auto map = map_t();
|
||||
map.reserve(51);
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <app/counter.h>
|
||||
#include <app/counting_allocator.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <third-party/nanobench.h>
|
||||
|
||||
#include <deque>
|
||||
|
||||
TEST_CASE("segmented_vector") {
|
||||
counter counts;
|
||||
INFO(counts);
|
||||
{
|
||||
auto vec = ankerl::unordered_dense::segmented_vector<counter::obj>();
|
||||
for (size_t i = 0; i < 1000; ++i) {
|
||||
vec.emplace_back(i, counts);
|
||||
REQUIRE(i + 1 == counts.ctor());
|
||||
}
|
||||
REQUIRE(0 == counts.move_ctor());
|
||||
REQUIRE(0 == counts.move_assign());
|
||||
counts("before dtor");
|
||||
REQUIRE(counts.data() == counter::data_t{1000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
|
||||
}
|
||||
counts.check_all_done();
|
||||
REQUIRE(0 == counts.move_ctor());
|
||||
counts("done");
|
||||
REQUIRE(counts.data() == counter::data_t{1000, 0, 0, 1000, 0, 0, 0, 0, 0, 0, 0, 0, 0});
|
||||
}
|
||||
|
||||
TEST_CASE("segmented_vector_capacity") {
|
||||
counter counts;
|
||||
INFO(counts);
|
||||
auto vec =
|
||||
ankerl::unordered_dense::segmented_vector<counter::obj, std::allocator<counter::obj>, sizeof(counter::obj) * 4>();
|
||||
REQUIRE(0 == vec.capacity());
|
||||
for (size_t i = 0; i < 50; ++i) {
|
||||
REQUIRE(i == vec.size());
|
||||
vec.emplace_back(i, counts);
|
||||
REQUIRE(i + 1 == vec.size());
|
||||
REQUIRE(vec.capacity() >= vec.size());
|
||||
REQUIRE(0 == vec.capacity() % 4);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("segmented_vector_idx") {
|
||||
counter counts;
|
||||
INFO(counts);
|
||||
auto vec =
|
||||
ankerl::unordered_dense::segmented_vector<counter::obj, std::allocator<counter::obj>, sizeof(counter::obj) * 4>();
|
||||
REQUIRE(0 == vec.capacity());
|
||||
for (size_t i = 0; i < 50; ++i) {
|
||||
vec.emplace_back(i, counts);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < vec.size(); ++i) {
|
||||
REQUIRE(i == vec[i].get());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("segmented_vector_iterate") {
|
||||
counter counts;
|
||||
INFO(counts);
|
||||
auto vec =
|
||||
ankerl::unordered_dense::segmented_vector<counter::obj, std::allocator<counter::obj>, sizeof(counter::obj) * 4>();
|
||||
for (size_t i = 0; i < 50; ++i) {
|
||||
auto it = vec.begin();
|
||||
auto end = vec.end();
|
||||
|
||||
REQUIRE(std::distance(it, end) == static_cast<std::ptrdiff_t>(vec.size()));
|
||||
size_t j = 0;
|
||||
while (it != end) {
|
||||
REQUIRE(it->get() == j);
|
||||
++it;
|
||||
++j;
|
||||
}
|
||||
vec.emplace_back(i, counts);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("segmented_vector_reserve") {
|
||||
auto counts = counts_for_allocator{};
|
||||
auto vec = ankerl::unordered_dense::segmented_vector<int, counting_allocator<int>, sizeof(int) * 16>(&counts);
|
||||
|
||||
REQUIRE(0 == vec.capacity());
|
||||
REQUIRE(counts.size() < 2);
|
||||
vec.reserve(1100);
|
||||
REQUIRE(counts.size() > 63);
|
||||
counts.reset();
|
||||
REQUIRE(counts.size() == 0);
|
||||
REQUIRE(1104 == vec.capacity());
|
||||
|
||||
for (size_t i = 0; i < vec.capacity(); ++i) {
|
||||
vec.emplace_back(0);
|
||||
}
|
||||
REQUIRE(counts.size() == 0);
|
||||
vec.emplace_back(123);
|
||||
|
||||
// 3: 2 for std::vector<T*> reallocates, 1 for the new segment
|
||||
REQUIRE(counts.size() == 3);
|
||||
}
|
||||
|
||||
using vec_t = ankerl::unordered_dense::segmented_vector<counter::obj>;
|
||||
static_assert(sizeof(vec_t) == sizeof(std::vector<counter::obj*>) + sizeof(size_t));
|
||||
|
||||
TEST_CASE("bench_segmented_vector" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
static constexpr auto num_elements = size_t{21233};
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
ankerl::nanobench::Rng rng(123);
|
||||
|
||||
auto sv = ankerl::unordered_dense::segmented_vector<size_t>();
|
||||
for (size_t i = 0; i < num_elements; ++i) {
|
||||
sv.emplace_back(i);
|
||||
}
|
||||
|
||||
ankerl::nanobench::Bench().minEpochTime(100ms).batch(sv.size()).run("shuffle stable_vector", [&] {
|
||||
rng.shuffle(sv);
|
||||
});
|
||||
|
||||
auto c = std::deque<size_t>();
|
||||
for (size_t i = 0; i < num_elements; ++i) {
|
||||
c.push_back(i);
|
||||
}
|
||||
ankerl::nanobench::Bench().minEpochTime(100ms).batch(sv.size()).run("shuffle std::deque", [&] {
|
||||
rng.shuffle(c);
|
||||
});
|
||||
|
||||
auto v = std::vector<size_t>();
|
||||
for (size_t i = 0; i < num_elements; ++i) {
|
||||
v.push_back(i);
|
||||
}
|
||||
ankerl::nanobench::Bench().minEpochTime(100ms).batch(sv.size()).run("shuffle std::vector", [&] {
|
||||
rng.shuffle(v);
|
||||
});
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <string> // for operator==, basic_string, operat...
|
||||
#include <vector> // for vector
|
||||
|
||||
TEST_CASE_SET("set", std::string) {
|
||||
auto set = set_t();
|
||||
|
||||
set.insert("asdf");
|
||||
REQUIRE(set.size() == 1);
|
||||
auto it = set.find("asdf");
|
||||
REQUIRE(it != set.end());
|
||||
REQUIRE(*it == "asdf");
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
template <typename T>
|
||||
using detect_has_mapped_type = typename T::mapped_type;
|
||||
|
||||
using map1_t = ankerl::unordered_dense::map<int, double>;
|
||||
static_assert(std::is_same_v<double, map1_t::mapped_type>);
|
||||
static_assert(ankerl::unordered_dense::detail::is_detected_v<detect_has_mapped_type, map1_t>);
|
||||
|
||||
using map2_t = ankerl::unordered_dense::segmented_map<int, double>;
|
||||
static_assert(std::is_same_v<double, map2_t::mapped_type>);
|
||||
static_assert(ankerl::unordered_dense::detail::is_detected_v<detect_has_mapped_type, map2_t>);
|
||||
|
||||
using set1_t = ankerl::unordered_dense::set<int>;
|
||||
static_assert(!ankerl::unordered_dense::detail::is_detected_v<detect_has_mapped_type, set1_t>);
|
||||
|
||||
using set2_t = ankerl::unordered_dense::segmented_set<int>;
|
||||
static_assert(!ankerl::unordered_dense::detail::is_detected_v<detect_has_mapped_type, set2_t>);
|
||||
@@ -0,0 +1,33 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <doctest.h>
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <string> // for allocator, string, operator==
|
||||
#include <utility> // for pair, move
|
||||
#include <vector> // for vector
|
||||
|
||||
namespace {
|
||||
|
||||
struct foo {
|
||||
uint64_t m_i;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
template <>
|
||||
struct std::hash<foo> {
|
||||
auto operator()(foo const& foo) const noexcept {
|
||||
return static_cast<size_t>(foo.m_i + 1);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE("std_hash") {
|
||||
auto f = foo{12345};
|
||||
REQUIRE(std::hash<foo>{}(f) == 12346U);
|
||||
// unordered_dense::hash blows that up to 64bit!
|
||||
|
||||
// Just wraps std::hash
|
||||
REQUIRE(ankerl::unordered_dense::hash<foo>{}(f) == UINT64_C(12346));
|
||||
REQUIRE(ankerl::unordered_dense::hash<uint64_t>{}(12346U) == UINT64_C(0x3F645BE4CE24110C));
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#define ENABLE_LOG_LINE
|
||||
#include <app/doctest.h>
|
||||
#include <app/print.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
TEST_CASE_MAP("swap", int, int) {
|
||||
{
|
||||
auto b = map_t();
|
||||
{
|
||||
auto a = map_t();
|
||||
b[1] = 2;
|
||||
|
||||
a.swap(b);
|
||||
REQUIRE(a.end() != a.find(1));
|
||||
REQUIRE(b.end() == b.find(1));
|
||||
}
|
||||
REQUIRE(b.end() == b.find(1));
|
||||
b[2] = 3;
|
||||
REQUIRE(b.end() != b.find(2));
|
||||
REQUIRE(b.size() == 1);
|
||||
}
|
||||
|
||||
{
|
||||
auto a = map_t();
|
||||
{
|
||||
auto b = map_t();
|
||||
b[1] = 2;
|
||||
|
||||
a.swap(b);
|
||||
REQUIRE(a.end() != a.find(1));
|
||||
REQUIRE(b.end() == b.find(1));
|
||||
}
|
||||
REQUIRE(a.end() != a.find(1));
|
||||
a[2] = 3;
|
||||
REQUIRE(a.end() != a.find(2));
|
||||
REQUIRE(a.size() == 2);
|
||||
}
|
||||
|
||||
{
|
||||
auto a = map_t();
|
||||
{
|
||||
auto b = map_t();
|
||||
a.swap(b);
|
||||
REQUIRE(a.end() == a.find(1));
|
||||
REQUIRE(b.end() == b.find(1));
|
||||
}
|
||||
REQUIRE(a.end() == a.find(1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,420 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
#include <app/name_of_type.h>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <array> // for array
|
||||
#include <cstddef> // for size_t
|
||||
#include <functional> // for equal_to
|
||||
#include <string> // for basic_string, string, operator""s
|
||||
#include <string_view> // for basic_string_view, operator""sv
|
||||
#include <type_traits> // for add_const_t
|
||||
#include <unordered_map> // for unordered_map
|
||||
#include <utility> // for pair, as_const
|
||||
#include <vector> // for vector
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
class string_eq {
|
||||
mutable std::unordered_map<std::string, size_t> m_names_to_counts{};
|
||||
|
||||
public:
|
||||
using is_transparent = void;
|
||||
|
||||
template <class T, class U>
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay)
|
||||
auto operator()(T&& lhs, U&& rhs) const -> decltype(std::forward<T>(lhs) == std::forward<U>(rhs)) {
|
||||
auto names = fmt::format("{} -> {}", name_of_type<T>(), name_of_type<U>());
|
||||
++m_names_to_counts[names];
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay,hicpp-no-array-decay)
|
||||
return std::forward<T>(lhs) == std::forward<U>(rhs);
|
||||
}
|
||||
|
||||
auto counts() const -> std::unordered_map<std::string, size_t> const& {
|
||||
return m_names_to_counts;
|
||||
}
|
||||
|
||||
string_eq() = default;
|
||||
~string_eq() = default;
|
||||
|
||||
string_eq(string_eq const&) = default;
|
||||
auto operator=(string_eq const&) -> string_eq& = default;
|
||||
|
||||
string_eq(string_eq&&) = delete;
|
||||
auto operator=(string_eq&&) -> string_eq& = delete;
|
||||
};
|
||||
|
||||
// transparent hash, counts number of calls per operator
|
||||
class string_hash {
|
||||
mutable size_t m_num_charstar{};
|
||||
mutable size_t m_num_stringview{};
|
||||
mutable size_t m_num_string{};
|
||||
|
||||
public:
|
||||
using hash_type = ankerl::unordered_dense::hash<std::string_view>;
|
||||
using is_transparent = void;
|
||||
using is_avalanching = void;
|
||||
|
||||
[[nodiscard]] auto operator()(const char* str) const noexcept -> uint64_t {
|
||||
++m_num_charstar;
|
||||
return hash_type{}(str);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator()(std::string_view str) const noexcept -> uint64_t {
|
||||
++m_num_stringview;
|
||||
return hash_type{}(str);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator()(std::string const& str) const noexcept -> uint64_t {
|
||||
++m_num_string;
|
||||
return hash_type{}(str);
|
||||
}
|
||||
|
||||
auto counts() const -> std::array<size_t, 3> {
|
||||
return {m_num_charstar, m_num_stringview, m_num_string};
|
||||
}
|
||||
};
|
||||
|
||||
namespace docs {
|
||||
|
||||
struct string_hash {
|
||||
using is_transparent = void; // enable heterogeneous lookup
|
||||
using is_avalanching = void; // mark class as high quality avalanching hash
|
||||
|
||||
[[nodiscard]] auto operator()(const char* str) const noexcept -> uint64_t {
|
||||
return ankerl::unordered_dense::hash<std::string_view>{}(str);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator()(std::string_view str) const noexcept -> uint64_t {
|
||||
return ankerl::unordered_dense::hash<std::string_view>{}(str);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator()(std::string const& str) const noexcept -> uint64_t {
|
||||
return ankerl::unordered_dense::hash<std::string_view>{}(str);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace docs
|
||||
|
||||
template <typename C>
|
||||
void check(int line, C const& container, size_t num_charstar, size_t num_stringview, size_t num_string) {
|
||||
auto sh = container.hash_function();
|
||||
auto counts = sh.counts();
|
||||
INFO("check line " << line << ": expect (" << num_charstar << ", " << num_stringview << ", " << num_string << ") got ("
|
||||
<< counts[0] << ", " << counts[1] << ", " << counts[2]
|
||||
<< ") for (num_charstar, num_stringview, num_string)");
|
||||
REQUIRE(counts[0] == num_charstar);
|
||||
REQUIRE(counts[1] == num_stringview);
|
||||
REQUIRE(counts[2] == num_string);
|
||||
}
|
||||
|
||||
TYPE_TO_STRING_MAP(std::string, size_t, string_hash, std::equal_to<>);
|
||||
|
||||
TEST_CASE_MAP("transparent_find", std::string, size_t, string_hash, std::equal_to<>) {
|
||||
auto map = map_t();
|
||||
map.try_emplace("hello", 1);
|
||||
check(__LINE__, map, 1, 0, 0);
|
||||
|
||||
auto it = map.find("huh");
|
||||
check(__LINE__, map, 2, 0, 0);
|
||||
REQUIRE(it == map.end());
|
||||
it = map.find("hello");
|
||||
check(__LINE__, map, 3, 0, 0);
|
||||
REQUIRE(it != map.end());
|
||||
|
||||
auto cit = std::as_const(map).find("huh");
|
||||
check(__LINE__, map, 4, 0, 0);
|
||||
REQUIRE(cit == map.end());
|
||||
REQUIRE(cit == map.cend());
|
||||
cit = std::as_const(map).find("hello");
|
||||
check(__LINE__, map, 5, 0, 0);
|
||||
REQUIRE(cit != map.end());
|
||||
|
||||
// string_view
|
||||
it = map.find("huh"sv);
|
||||
REQUIRE(it == map.end());
|
||||
check(__LINE__, map, 5, 1, 0);
|
||||
it = map.find("hello"sv);
|
||||
REQUIRE(it != map.end());
|
||||
check(__LINE__, map, 5, 2, 0);
|
||||
|
||||
// string
|
||||
it = map.find("huh"s);
|
||||
REQUIRE(it == map.end());
|
||||
check(__LINE__, map, 5, 2, 1);
|
||||
it = map.find("hello"s);
|
||||
REQUIRE(it != map.end());
|
||||
check(__LINE__, map, 5, 2, 2);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("transparent_count", std::string, size_t, string_hash, std::equal_to<>) {
|
||||
auto map = map_t();
|
||||
map.try_emplace("hello", 1);
|
||||
check(__LINE__, map, 1, 0, 0);
|
||||
|
||||
REQUIRE(0 == map.count("huh"));
|
||||
check(__LINE__, map, 2, 0, 0);
|
||||
REQUIRE(1 == map.count("hello"));
|
||||
check(__LINE__, map, 3, 0, 0);
|
||||
|
||||
REQUIRE(0 == map.count("huh"sv));
|
||||
check(__LINE__, map, 3, 1, 0);
|
||||
REQUIRE(1 == map.count("hello"sv));
|
||||
check(__LINE__, map, 3, 2, 0);
|
||||
|
||||
REQUIRE(0 == map.count("huh"s));
|
||||
check(__LINE__, map, 3, 2, 1);
|
||||
REQUIRE(1 == map.count("hello"s));
|
||||
check(__LINE__, map, 3, 2, 2);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("transparent_contains", std::string, size_t, string_hash, std::equal_to<>) {
|
||||
auto map = map_t();
|
||||
map.try_emplace("hello", 1);
|
||||
check(__LINE__, map, 1, 0, 0);
|
||||
|
||||
REQUIRE(!map.contains("huh"));
|
||||
check(__LINE__, map, 2, 0, 0);
|
||||
REQUIRE(map.contains("hello"));
|
||||
check(__LINE__, map, 3, 0, 0);
|
||||
|
||||
REQUIRE(!map.contains("huh"sv));
|
||||
check(__LINE__, map, 3, 1, 0);
|
||||
REQUIRE(map.contains("hello"sv));
|
||||
check(__LINE__, map, 3, 2, 0);
|
||||
|
||||
REQUIRE(!map.contains("huh"s));
|
||||
check(__LINE__, map, 3, 2, 1);
|
||||
REQUIRE(map.contains("hello"s));
|
||||
check(__LINE__, map, 3, 2, 2);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("transparent_erase", std::string, size_t, string_hash, std::equal_to<>) {
|
||||
auto map = map_t();
|
||||
map.try_emplace("hello", 1);
|
||||
check(__LINE__, map, 1, 0, 0);
|
||||
REQUIRE(0 == map.erase("huh"));
|
||||
check(__LINE__, map, 2, 0, 0);
|
||||
REQUIRE(1 == map.erase("hello"));
|
||||
check(__LINE__, map, 3, 0, 0);
|
||||
|
||||
map.try_emplace("hello", 1);
|
||||
check(__LINE__, map, 4, 0, 0);
|
||||
REQUIRE(0 == map.erase("huh"sv));
|
||||
check(__LINE__, map, 4, 1, 0);
|
||||
REQUIRE(1 == map.erase("hello"sv));
|
||||
check(__LINE__, map, 4, 2, 0);
|
||||
|
||||
map.try_emplace("hello", 1);
|
||||
check(__LINE__, map, 5, 2, 0);
|
||||
REQUIRE(0 == map.erase("huh"s));
|
||||
check(__LINE__, map, 5, 2, 1);
|
||||
REQUIRE(1 == map.erase("hello"s));
|
||||
check(__LINE__, map, 5, 2, 2);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("transparent_equal_range", std::string, size_t, string_hash, std::equal_to<>) {
|
||||
auto map = map_t();
|
||||
map.try_emplace("hello", 1);
|
||||
check(__LINE__, map, 1, 0, 0);
|
||||
|
||||
auto range = map.equal_range("hello");
|
||||
check(__LINE__, map, 2, 0, 0);
|
||||
REQUIRE(range.first != range.second);
|
||||
REQUIRE(range.first->first == "hello");
|
||||
REQUIRE(range.second == map.end());
|
||||
|
||||
auto crange = std::as_const(map).equal_range("hello"sv);
|
||||
check(__LINE__, map, 2, 1, 0);
|
||||
REQUIRE(crange.first != range.second);
|
||||
REQUIRE(crange.first->first == "hello");
|
||||
REQUIRE(crange.second == map.end());
|
||||
}
|
||||
|
||||
TYPE_TO_STRING_MAP(std::string, size_t, string_hash, string_eq);
|
||||
|
||||
TEST_CASE_MAP("transparent_string_eq", std::string, size_t, string_hash, string_eq) {
|
||||
auto map = map_t();
|
||||
map.try_emplace("hello", 1);
|
||||
|
||||
REQUIRE(map.count("hello"));
|
||||
REQUIRE(map.count("hello"sv));
|
||||
REQUIRE(map.count("hello"s));
|
||||
|
||||
auto ke = map.key_eq();
|
||||
REQUIRE(ke.counts().size() == 3);
|
||||
for (auto const& kv : ke.counts()) {
|
||||
REQUIRE(kv.second == 1U);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("transparent_at", std::string, size_t, string_hash, string_eq) {
|
||||
auto map = map_t();
|
||||
map.try_emplace("asdf", 123);
|
||||
check(__LINE__, map, 1, 0, 0);
|
||||
|
||||
auto& vt = map.at("asdf");
|
||||
check(__LINE__, map, 2, 0, 0);
|
||||
REQUIRE(vt == 123);
|
||||
|
||||
// NOLINTNEXTLINE(llvm-else-after-return,readability-else-after-return)
|
||||
REQUIRE_THROWS_AS(map.at("nope"sv), std::out_of_range);
|
||||
check(__LINE__, map, 2, 1, 0);
|
||||
}
|
||||
|
||||
TYPE_TO_STRING_MAP(std::string, size_t, string_hash);
|
||||
|
||||
TEST_CASE_MAP("transparent_at_not", std::string, size_t, string_hash) {
|
||||
// no string_eq, so not is_transparent
|
||||
auto map = map_t();
|
||||
map.try_emplace("asdf", 123);
|
||||
check(__LINE__, map, 0, 0, 1);
|
||||
|
||||
auto& vt = map.at("asdf");
|
||||
check(__LINE__, map, 0, 0, 2);
|
||||
REQUIRE(vt == 123);
|
||||
|
||||
// NOLINTNEXTLINE(llvm-else-after-return,readability-else-after-return)
|
||||
REQUIRE_THROWS_AS(map.at("nope"), std::out_of_range);
|
||||
check(__LINE__, map, 0, 0, 3);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("transparent_insert_or_assign", std::string, size_t, string_hash, string_eq) {
|
||||
auto map = map_t();
|
||||
auto r = map.insert_or_assign("asdf", 123U);
|
||||
check(__LINE__, map, 1, 0, 0);
|
||||
REQUIRE(r.first->first == "asdf");
|
||||
REQUIRE(r.first->second == 123U);
|
||||
REQUIRE(r.second);
|
||||
|
||||
r = map.insert_or_assign("asdf", 42U);
|
||||
check(__LINE__, map, 2, 0, 0);
|
||||
REQUIRE(r.first->first == "asdf");
|
||||
REQUIRE(r.first->second == 42U);
|
||||
REQUIRE(!r.second);
|
||||
REQUIRE(map.size() == 1U);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("transparent_insert_or_assign_not", std::string, size_t, string_hash) {
|
||||
auto map = map_t();
|
||||
auto r = map.insert_or_assign("asdf", 123U);
|
||||
check(__LINE__, map, 0, 0, 1);
|
||||
REQUIRE(r.first->first == "asdf");
|
||||
REQUIRE(r.first->second == 123U);
|
||||
REQUIRE(r.second);
|
||||
|
||||
r = map.insert_or_assign("asdf", 42U);
|
||||
check(__LINE__, map, 0, 0, 2);
|
||||
REQUIRE(r.first->first == "asdf");
|
||||
REQUIRE(r.first->second == 42U);
|
||||
REQUIRE(!r.second);
|
||||
REQUIRE(map.size() == 1U);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("transparent_insert_or_assign_iterator", std::string, size_t, string_hash, string_eq) {
|
||||
auto map = map_t();
|
||||
auto r = map.insert_or_assign(typename map_t::const_iterator{}, "asdf", 123U);
|
||||
check(__LINE__, map, 1, 0, 0);
|
||||
REQUIRE(r->first == "asdf");
|
||||
REQUIRE(r->second == 123U);
|
||||
|
||||
r = map.insert_or_assign(typename map_t::const_iterator{}, "asdf", 42U);
|
||||
check(__LINE__, map, 2, 0, 0);
|
||||
REQUIRE(r->first == "asdf");
|
||||
REQUIRE(r->second == 42U);
|
||||
REQUIRE(map.size() == 1U);
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("transparent_insert_or_assign_iterator_not", std::string, size_t, string_hash) {
|
||||
auto map = map_t();
|
||||
auto r = map.insert_or_assign(typename map_t::const_iterator{}, "asdf", 123U);
|
||||
check(__LINE__, map, 0, 0, 1);
|
||||
REQUIRE(r->first == "asdf");
|
||||
REQUIRE(r->second == 123U);
|
||||
|
||||
r = map.insert_or_assign(typename map_t::const_iterator{}, "asdf", 42U);
|
||||
check(__LINE__, map, 0, 0, 2);
|
||||
REQUIRE(r->first == "asdf");
|
||||
REQUIRE(r->second == 42U);
|
||||
REQUIRE(map.size() == 1U);
|
||||
}
|
||||
|
||||
TYPE_TO_STRING_SET(std::string, string_hash, string_eq);
|
||||
|
||||
// insert() transparent is only possible with unordered_set, not with unordered_map
|
||||
TEST_CASE_SET("transparent_set_insert", std::string, string_hash, string_eq) {
|
||||
auto set = set_t();
|
||||
set.insert("abcdefg");
|
||||
check(__LINE__, set, 1, 0, 0);
|
||||
set.insert("abcdefg");
|
||||
check(__LINE__, set, 2, 0, 0);
|
||||
}
|
||||
|
||||
TYPE_TO_STRING_SET(std::string, string_hash);
|
||||
|
||||
// insert() transparent is only possible with unordered_set, not with unordered_map
|
||||
TEST_CASE_SET("transparent_set_insert_not", std::string, string_hash) {
|
||||
auto set = set_t();
|
||||
set.insert("abcdefg");
|
||||
check(__LINE__, set, 0, 0, 1);
|
||||
set.insert("abcdefg");
|
||||
check(__LINE__, set, 0, 0, 2);
|
||||
}
|
||||
|
||||
// emplace() transparent is only possible with unordered_set, not with unordered_map
|
||||
TEST_CASE_SET("transparent_set_emplace", std::string, string_hash, string_eq) {
|
||||
auto set = set_t();
|
||||
set.emplace("abcdefg");
|
||||
check(__LINE__, set, 1, 0, 0);
|
||||
set.emplace("abcdefg");
|
||||
check(__LINE__, set, 2, 0, 0);
|
||||
}
|
||||
|
||||
// emplace() transparent is only possible with unordered_set, not with unordered_map
|
||||
TEST_CASE_SET("transparent_set_emplace_not", std::string, string_hash) {
|
||||
auto set = set_t();
|
||||
set.emplace("abcdefg");
|
||||
check(__LINE__, set, 0, 0, 1);
|
||||
set.emplace("abcdefg");
|
||||
check(__LINE__, set, 0, 0, 2);
|
||||
}
|
||||
|
||||
struct string_hash_simple {
|
||||
using is_transparent = void; // enable heterogeneous lookup
|
||||
using is_avalanching = void; // mark class as high quality avalanching hash
|
||||
|
||||
[[nodiscard]] auto operator()(std::string_view str) const noexcept -> uint64_t {
|
||||
return ankerl::unordered_dense::hash<std::string_view>{}(str);
|
||||
}
|
||||
};
|
||||
|
||||
TYPE_TO_STRING_MAP(std::string, size_t, string_hash_simple, std::equal_to<>);
|
||||
|
||||
TEST_CASE_MAP("transparent_find_simple", std::string, size_t, string_hash_simple, std::equal_to<>) {
|
||||
auto map = map_t();
|
||||
map.try_emplace("hello", 1);
|
||||
auto it = map.find("huh");
|
||||
REQUIRE(it == map.end());
|
||||
it = map.find("hello");
|
||||
REQUIRE(it != map.end());
|
||||
|
||||
auto cit = std::as_const(map).find("huh");
|
||||
REQUIRE(cit == map.end());
|
||||
REQUIRE(cit == map.cend());
|
||||
cit = std::as_const(map).find("hello");
|
||||
REQUIRE(cit != map.end());
|
||||
|
||||
// string_view
|
||||
it = map.find("huh"sv);
|
||||
REQUIRE(it == map.end());
|
||||
it = map.find("hello"sv);
|
||||
REQUIRE(it != map.end());
|
||||
|
||||
// string
|
||||
it = map.find("huh"s);
|
||||
REQUIRE(it == map.end());
|
||||
it = map.find("hello"s);
|
||||
REQUIRE(it != map.end());
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <string> // for allocator, string, operator==
|
||||
#include <utility> // for pair, move
|
||||
#include <vector> // for vector
|
||||
|
||||
struct regular_type {
|
||||
// cppcheck-suppress passedByValue
|
||||
regular_type(std::size_t i, std::string s) noexcept
|
||||
: m_s(std::move(s))
|
||||
, m_i(i) {}
|
||||
|
||||
friend auto operator==(const regular_type& r1, const regular_type& r2) -> bool {
|
||||
return r1.m_i == r2.m_i && r1.m_s == r2.m_s;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_s;
|
||||
std::size_t m_i;
|
||||
};
|
||||
|
||||
TYPE_TO_STRING_MAP(std::string, regular_type);
|
||||
|
||||
TEST_CASE_MAP("try_emplace", std::string, regular_type) {
|
||||
map_t map;
|
||||
auto ret = map.try_emplace("a", 1U, "b");
|
||||
REQUIRE(ret.second);
|
||||
REQUIRE(ret.first == map.find("a"));
|
||||
REQUIRE(ret.first->second == regular_type(1U, "b"));
|
||||
REQUIRE(map.size() == 1);
|
||||
|
||||
ret = map.try_emplace("c", 2U, "d");
|
||||
REQUIRE(ret.second);
|
||||
REQUIRE(ret.first == map.find("c"));
|
||||
REQUIRE(ret.first->second == regular_type(2U, "d"));
|
||||
REQUIRE(map.size() == 2U);
|
||||
|
||||
std::string key = "c";
|
||||
ret = map.try_emplace(key, 3U, "dd");
|
||||
REQUIRE_FALSE(ret.second);
|
||||
REQUIRE(ret.first == map.find("c"));
|
||||
REQUIRE(ret.first->second == regular_type(2U, "d"));
|
||||
REQUIRE(map.size() == 2U);
|
||||
|
||||
key = "a";
|
||||
regular_type value(3U, "dd");
|
||||
ret = map.try_emplace(key, value);
|
||||
REQUIRE_FALSE(ret.second);
|
||||
REQUIRE(ret.first == map.find("a"));
|
||||
REQUIRE(ret.first->second == regular_type(1U, "b"));
|
||||
REQUIRE(map.size() == 2U);
|
||||
|
||||
auto pos = map.try_emplace(map.end(), "e", 67U, "f");
|
||||
REQUIRE(pos == map.find("e"));
|
||||
REQUIRE(pos->second == regular_type(67U, "f"));
|
||||
REQUIRE(map.size() == 3U);
|
||||
|
||||
key = "e";
|
||||
regular_type value2(66U, "ff");
|
||||
pos = map.try_emplace(map.begin(), key, value2);
|
||||
REQUIRE(pos == map.find("e"));
|
||||
REQUIRE(pos->second == regular_type(67U, "f"));
|
||||
REQUIRE(map.size() == 3U);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#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>>{};
|
||||
REQUIRE(pair_hash(std::pair<int, std::string>{1, "a"}) != pair_hash(std::pair<int, std::string>{1, "b"}));
|
||||
|
||||
m.try_emplace({1, "a"}, 23);
|
||||
m.try_emplace({1, "b"}, 42);
|
||||
REQUIRE(m.size() == 2U);
|
||||
}
|
||||
|
||||
TEST_CASE("good_tuple_hash") {
|
||||
auto hashes = ankerl::unordered_dense::set<uint64_t>();
|
||||
|
||||
auto t = std::tuple<uint8_t, uint8_t, uint8_t>();
|
||||
for (size_t i = 0; i < 256 * 256; ++i) {
|
||||
std::get<0>(t) = static_cast<uint8_t>(i);
|
||||
std::get<2>(t) = static_cast<uint8_t>(i / 256);
|
||||
hashes.emplace(ankerl::unordered_dense::hash<decltype(t)>{}(t));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// #include <absl/hash/hash.h>
|
||||
|
||||
TEST_CASE("bench_tuple_hash" * doctest::test_suite("bench") * doctest::skip()) {
|
||||
using T = std::tuple<uint8_t, int, uint16_t, uint64_t>;
|
||||
|
||||
auto vecs = std::vector<T>(100);
|
||||
auto rng = ankerl::nanobench::Rng(123);
|
||||
for (auto& v : vecs) {
|
||||
std::get<0>(v) = static_cast<uint8_t>(rng());
|
||||
std::get<1>(v) = static_cast<int>(rng());
|
||||
std::get<2>(v) = static_cast<uint16_t>(rng());
|
||||
std::get<3>(v) = static_cast<uint64_t>(rng());
|
||||
}
|
||||
|
||||
uint64_t h = 0;
|
||||
ankerl::nanobench::Bench().batch(vecs.size()).run("ankerl hash", [&] {
|
||||
for (auto const& v : vecs) {
|
||||
h += ankerl::unordered_dense::hash<T>{}(v);
|
||||
// h += absl::Hash<T>{}(v);
|
||||
}
|
||||
});
|
||||
ankerl::nanobench::doNotOptimizeAway(h);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <memory> // for operator!=, unique_ptr, make_unique
|
||||
#include <utility> // for move, pair
|
||||
#include <vector> // for vector
|
||||
|
||||
TYPE_TO_STRING_MAP(size_t, std::unique_ptr<int>);
|
||||
|
||||
TEST_CASE_MAP("unique_ptr", size_t, std::unique_ptr<int>) {
|
||||
map_t m;
|
||||
REQUIRE(m.end() == m.find(123));
|
||||
REQUIRE(m.end() == m.begin());
|
||||
m[static_cast<size_t>(32)] = std::make_unique<int>(123);
|
||||
REQUIRE(m.end() != m.begin());
|
||||
REQUIRE(m.end() == m.find(123));
|
||||
REQUIRE(m.end() != m.find(32));
|
||||
|
||||
for (auto const& kv : m) {
|
||||
REQUIRE(kv.first == 32);
|
||||
REQUIRE(kv.second != nullptr);
|
||||
REQUIRE(*kv.second == 123);
|
||||
}
|
||||
|
||||
m = map_t();
|
||||
REQUIRE(m.end() == m.begin());
|
||||
REQUIRE(m.end() == m.find(123));
|
||||
REQUIRE(m.end() == m.find(32));
|
||||
|
||||
map_t empty;
|
||||
map_t m3(std::move(empty));
|
||||
REQUIRE(m3.end() == m3.begin());
|
||||
REQUIRE(m3.end() == m3.find(123));
|
||||
REQUIRE(m3.end() == m3.find(32));
|
||||
m3[static_cast<size_t>(32)];
|
||||
REQUIRE(m3.end() != m3.begin());
|
||||
REQUIRE(m3.end() == m3.find(123));
|
||||
REQUIRE(m3.end() != m3.find(32));
|
||||
|
||||
empty = map_t{};
|
||||
map_t m4(std::move(empty));
|
||||
REQUIRE(m4.count(123) == 0);
|
||||
REQUIRE(m4.end() == m4.begin());
|
||||
REQUIRE(m4.end() == m4.find(123));
|
||||
REQUIRE(m4.end() == m4.find(32));
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("unique_ptr_fill", size_t, std::unique_ptr<int>) {
|
||||
map_t m;
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
// m.emplace(i % 500, std::make_unique<int>(i));
|
||||
m.emplace(static_cast<size_t>(i), new int(i)); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
// element is still constructed, so there's no memory leak here.
|
||||
// Boost 1.80 behaves differently
|
||||
m.emplace(static_cast<size_t>(i), new int(i)); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <cstddef> // for size_t
|
||||
#include <cstdint> // for UINT64_C, uint64_t
|
||||
#include <string> // for basic_string, allocator, operator==
|
||||
#include <type_traits> // for integral_constant<>::value, is_same
|
||||
#include <vector> // for vector
|
||||
|
||||
TEST_CASE("unordered_set_asserts") {
|
||||
using set1_t = ankerl::unordered_dense::set<uint64_t>;
|
||||
static_assert(std::is_same<typename set1_t::key_type, uint64_t>::value, "key_type same");
|
||||
static_assert(std::is_same<typename set1_t::value_type, uint64_t>::value, "value_type same");
|
||||
|
||||
using set2_t = ankerl::unordered_dense::segmented_set<uint64_t>;
|
||||
static_assert(std::is_same<typename set2_t::key_type, uint64_t>::value, "key_type same");
|
||||
static_assert(std::is_same<typename set2_t::value_type, uint64_t>::value, "value_type same");
|
||||
}
|
||||
|
||||
TEST_CASE_SET("unordered_set", uint64_t) {
|
||||
set_t set;
|
||||
set.emplace(UINT64_C(123));
|
||||
REQUIRE(set.size() == 1U);
|
||||
|
||||
set.insert(UINT64_C(333));
|
||||
REQUIRE(set.size() == 2U);
|
||||
|
||||
set.erase(UINT64_C(222));
|
||||
REQUIRE(set.size() == 2U);
|
||||
|
||||
set.erase(UINT64_C(123));
|
||||
REQUIRE(set.size() == 1U);
|
||||
}
|
||||
|
||||
TEST_CASE_SET("unordered_set_string", std::string) {
|
||||
set_t set;
|
||||
REQUIRE(set.begin() == set.end());
|
||||
|
||||
set.emplace(static_cast<size_t>(2000), 'a');
|
||||
REQUIRE(set.size() == 1);
|
||||
|
||||
REQUIRE(set.begin() != set.end());
|
||||
std::string const& str = *set.begin();
|
||||
REQUIRE(str == std::string(static_cast<size_t>(2000), 'a'));
|
||||
|
||||
auto it = set.begin();
|
||||
REQUIRE(++it == set.end());
|
||||
}
|
||||
|
||||
TEST_CASE_SET("unordered_set_eq", std::string) {
|
||||
set_t set1;
|
||||
set_t set2;
|
||||
REQUIRE(set1.size() == set2.size());
|
||||
REQUIRE(set1 == set2);
|
||||
REQUIRE(set2 == set1);
|
||||
|
||||
set1.emplace("asdf");
|
||||
// (asdf) == ()
|
||||
REQUIRE(set1.size() != set2.size());
|
||||
REQUIRE(set1 != set2);
|
||||
REQUIRE(set2 != set1);
|
||||
|
||||
set2.emplace("huh");
|
||||
// (asdf) == (huh)
|
||||
REQUIRE(set1.size() == set2.size());
|
||||
REQUIRE(set1 != set2);
|
||||
REQUIRE(set2 != set1);
|
||||
|
||||
set1.emplace("huh");
|
||||
// (asdf, huh) == (huh)
|
||||
REQUIRE(set1.size() != set2.size());
|
||||
REQUIRE(set1 != set2);
|
||||
REQUIRE(set2 != set1);
|
||||
|
||||
set2.emplace("asdf");
|
||||
// (asdf, huh) == (asdf, huh)
|
||||
REQUIRE(set1.size() == set2.size());
|
||||
REQUIRE(set1 == set2);
|
||||
REQUIRE(set2 == set1);
|
||||
|
||||
set1.erase("asdf");
|
||||
// (huh) == (asdf, huh)
|
||||
REQUIRE(set1.size() != set2.size());
|
||||
REQUIRE(set1 != set2);
|
||||
REQUIRE(set2 != set1);
|
||||
|
||||
set2.erase("asdf");
|
||||
// (huh) == (huh)
|
||||
REQUIRE(set1.size() == set2.size());
|
||||
REQUIRE(set1 == set2);
|
||||
REQUIRE(set2 == set1);
|
||||
|
||||
set1.clear();
|
||||
// () == (huh)
|
||||
REQUIRE(set1.size() != set2.size());
|
||||
REQUIRE(set1 != set2);
|
||||
REQUIRE(set2 != set1);
|
||||
|
||||
set2.erase("huh");
|
||||
// () == ()
|
||||
REQUIRE(set1.size() == set2.size());
|
||||
REQUIRE(set1 == set2);
|
||||
REQUIRE(set2 == set1);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include <app/counter.h>
|
||||
#include <app/doctest.h>
|
||||
|
||||
#include <third-party/nanobench.h>
|
||||
|
||||
#include <algorithm> // for max
|
||||
#include <cstddef> // for size_t
|
||||
#include <utility> // for move
|
||||
#include <vector> // for vector
|
||||
|
||||
template <typename Map>
|
||||
void fill(counter& counts, Map& map, ankerl::nanobench::Rng& rng) {
|
||||
auto n = rng.bounded(20);
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
auto a = rng.bounded(20);
|
||||
auto b = rng.bounded(20);
|
||||
map.try_emplace({a, counts}, b, counts);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_MAP("vectormap", counter::obj, counter::obj) {
|
||||
auto counts = counter();
|
||||
INFO(counts);
|
||||
|
||||
auto rng = ankerl::nanobench::Rng(32154);
|
||||
{
|
||||
counts("begin");
|
||||
std::vector<map_t> maps;
|
||||
|
||||
// copies
|
||||
for (size_t i = 0; i < 10; ++i) {
|
||||
map_t m;
|
||||
fill(counts, m, rng);
|
||||
maps.push_back(m);
|
||||
}
|
||||
counts("copies");
|
||||
|
||||
// move
|
||||
for (size_t i = 0; i < 10; ++i) {
|
||||
map_t m;
|
||||
fill(counts, m, rng);
|
||||
maps.push_back(std::move(m));
|
||||
}
|
||||
counts("move");
|
||||
|
||||
// emplace
|
||||
for (size_t i = 0; i < 10; ++i) {
|
||||
maps.emplace_back();
|
||||
fill(counts, maps.back(), rng);
|
||||
}
|
||||
counts("emplace");
|
||||
}
|
||||
counts("dtor");
|
||||
REQUIRE(counts.dtor() ==
|
||||
counts.ctor() + counts.static_default_ctor + counts.copy_ctor() + counts.default_ctor() + counts.move_ctor());
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
#if defined(__has_include)
|
||||
# if __has_include(<Windows.h>)
|
||||
# include <Windows.h>
|
||||
# pragma message("Windows.h included")
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#include <ankerl/unordered_dense.h>
|
||||
#include <app/counter.h>
|
||||
|
||||
#include <doctest.h>
|
||||
|
||||
#include <initializer_list>
|
||||
|
||||
TEST_CASE("unordered_dense_with_windows_h") {
|
||||
auto counts = counter();
|
||||
using map_t = ankerl::unordered_dense::map<counter::obj, counter::obj>;
|
||||
auto map = map_t();
|
||||
|
||||
{
|
||||
auto key = size_t{1};
|
||||
auto it = map.try_emplace(counter::obj(key, counts), counter::obj(key, counts)).first;
|
||||
REQUIRE(it != map.end());
|
||||
REQUIRE(it->first.get() == key);
|
||||
}
|
||||
{
|
||||
auto key = size_t{2};
|
||||
map.emplace(std::piecewise_construct, std::forward_as_tuple(key, counts), std::forward_as_tuple(key + 77, counts));
|
||||
}
|
||||
{
|
||||
auto key = size_t{3};
|
||||
map[counter::obj(key, counts)] = counter::obj(key + 123, counts);
|
||||
}
|
||||
{
|
||||
auto key = size_t{4};
|
||||
map.insert(std::pair<counter::obj, counter::obj>(counter::obj(key, counts), counter::obj(key, counts)));
|
||||
}
|
||||
{
|
||||
auto key = size_t{5};
|
||||
map.insert_or_assign(counter::obj(key, counts), counter::obj(key + 1, counts));
|
||||
}
|
||||
{
|
||||
auto key = size_t{6};
|
||||
map.erase(counter::obj(key, counts));
|
||||
}
|
||||
{ map = map_t{}; }
|
||||
{
|
||||
auto m = map_t{};
|
||||
m.swap(map);
|
||||
}
|
||||
{ map.clear(); }
|
||||
{
|
||||
auto s = size_t{7};
|
||||
map.rehash(s);
|
||||
}
|
||||
{
|
||||
auto s = size_t{8};
|
||||
map.reserve(s);
|
||||
}
|
||||
{
|
||||
auto key = size_t{9};
|
||||
auto it = map.find(counter::obj(key, counts));
|
||||
auto d = std::distance(map.begin(), it);
|
||||
REQUIRE(0 <= d);
|
||||
REQUIRE(d <= static_cast<std::ptrdiff_t>(map.size()));
|
||||
}
|
||||
{
|
||||
if (!map.empty()) {
|
||||
auto idx = static_cast<int>(map.size() / 2);
|
||||
auto it = map.cbegin() + idx;
|
||||
auto const& key = it->first;
|
||||
auto found_it = map.find(key);
|
||||
REQUIRE(it == found_it);
|
||||
}
|
||||
}
|
||||
{
|
||||
if (!map.empty()) {
|
||||
auto it = map.begin() + static_cast<int>(map.size() / 2);
|
||||
map.erase(it);
|
||||
}
|
||||
}
|
||||
{
|
||||
auto tmp = map_t();
|
||||
std::swap(tmp, map);
|
||||
}
|
||||
{
|
||||
map = std::initializer_list<std::pair<counter::obj, counter::obj>>{
|
||||
{{1, counts}, {2, counts}},
|
||||
{{3, counts}, {4, counts}},
|
||||
{{5, counts}, {6, counts}},
|
||||
};
|
||||
REQUIRE(map.size() == 3);
|
||||
}
|
||||
{
|
||||
auto first_idx = 0;
|
||||
auto last_idx = 0;
|
||||
if (!map.empty()) {
|
||||
first_idx = static_cast<int>(map.size() / 2);
|
||||
last_idx = static_cast<int>(map.size() / 2);
|
||||
if (first_idx > last_idx) {
|
||||
std::swap(first_idx, last_idx);
|
||||
}
|
||||
}
|
||||
map.erase(map.cbegin() + first_idx, map.cbegin() + last_idx);
|
||||
}
|
||||
{
|
||||
map.~map_t();
|
||||
counts.check_all_done();
|
||||
new (&map) map_t();
|
||||
}
|
||||
{
|
||||
bool b = true;
|
||||
std::erase_if(map, [&](map_t::value_type const& /*v*/) {
|
||||
b = !b;
|
||||
return b;
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user