Feature/concurrent node containers (#271)

* added concurrent node containers

* removed spurious typename

* added missing includes

* avoided unused param warning

* worked around Clang bug

* s/{}/() to work around GCC4.8 problems with aggregate initialization

* used /bigobj for cfoa/visit_tests.cpp

* suppressed localized maybe-uninitialized warnings

* fixed comments

* added /bigobj to cfoa/insert_tests.cpp

* instrumented double exact comparison to spot a spurious error

* fixed pedantic error

* refactored byte_span machinery

* compromised on sub-epsilon equality for doubles that should be identical

* documented boost::concurrent_node_(map|set)

* added concurrent_node_set

* added missing AlternativeType

* tested empty node insertion

* tested node_handle allocator management

* added nonassignable_allocator and node_handle_allocator_swap_tests

* fixed warning disabling

* silenced spurious GCC warning

* broadened scope of previous pragma

* broadened even more

* worked around spurious constexpr-related msvc-14.0 bug
https://godbolt.org/z/v78545Ebf

* added workaround back

* replaced previous workaround with built-in one

* added workaround back on top of built-in solution (which doesn't work 100% of the time)
This commit is contained in:
joaquintides
2024-08-25 18:34:58 +02:00
committed by GitHub
parent 35bdabf259
commit f734e399e3
58 changed files with 7007 additions and 338 deletions
+19 -2
View File
@@ -1,14 +1,17 @@
// Copyright 2024 Joaquin M Lopez Muoz.
// Copyright 2024 Joaquin M Lopez Munoz.
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at htT://www.boost.org/LICENSE_1_0.txt)
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifdef BOOST_UNORDERED_CFOA_TESTS
#include <boost/unordered/concurrent_flat_map.hpp>
#include <boost/unordered/concurrent_flat_set.hpp>
#include <boost/unordered/concurrent_node_map.hpp>
#include <boost/unordered/concurrent_node_set.hpp>
#else
#include "../helpers/unordered.hpp"
#endif
#include "../helpers/helpers.hpp"
#include "../helpers/test.hpp"
#include <boost/type_traits/make_void.hpp>
#include <memory>
@@ -62,7 +65,16 @@ template <class Container> void test_explicit_alloc_ctor_extract(
template <class Container> void test_explicit_alloc_ctor_extract(
Container& c, std::true_type)
{
#ifdef BOOST_UNORDERED_CFOA_TESTS
typename Container::key_type k;
c.cvisit_while([&](typename Container::value_type const & x) {
k = test::get_key<Container>(x);
return false;
});
auto n = c.extract(k);
#else
auto n = c.extract(c.begin());
#endif
c.insert(std::move(n));
n = c.extract(typename Container::key_type());
c.insert(std::move(n));
@@ -122,8 +134,13 @@ UNORDERED_AUTO_TEST (explicit_alloc_ctor) {
test_explicit_alloc_ctor<boost::concurrent_flat_map<int, int,
boost::hash<int>, std::equal_to<int>,
explicit_allocator<std::pair<const int, int> > > >();
test_explicit_alloc_ctor<boost::concurrent_node_map<int, int,
boost::hash<int>, std::equal_to<int>,
explicit_allocator<std::pair<const int, int> > > >();
test_explicit_alloc_ctor<boost::concurrent_flat_set<
int, boost::hash<int>, std::equal_to<int>, explicit_allocator<int> > >();
test_explicit_alloc_ctor<boost::concurrent_node_set<
int, boost::hash<int>, std::equal_to<int>, explicit_allocator<int> > >();
#elif defined(BOOST_UNORDERED_FOA_TESTS)
test_explicit_alloc_ctor<boost::unordered_flat_map<int, int,
boost::hash<int>, std::equal_to<int>,
@@ -0,0 +1,213 @@
// Copyright (C) 2024 Joaquin M Lopez Munoz
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include <boost/config.hpp>
#if defined(BOOST_GCC)
// Spurious maybe-uninitialized warnings with allocators contained
// in node handles.
// Maybe related to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108230
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
#ifdef BOOST_UNORDERED_CFOA_TESTS
#include <boost/unordered/concurrent_node_map.hpp>
#include <boost/unordered/concurrent_node_set.hpp>
#else
#include "../helpers/unordered.hpp"
#endif
#include "../helpers/test.hpp"
#include <boost/config/workaround.hpp>
#include <boost/core/allocator_access.hpp>
#include <memory>
#include <type_traits>
namespace {
template <class T> struct nonassignable_allocator
{
using value_type = T;
nonassignable_allocator() = default;
nonassignable_allocator(nonassignable_allocator const&) = default;
template <class U>
nonassignable_allocator(nonassignable_allocator<U> const&) {}
nonassignable_allocator& operator=(nonassignable_allocator const&) = delete;
T* allocate(std::size_t n)
{
return static_cast<T*>(::operator new(n * sizeof(T)));
}
void deallocate(T* p, std::size_t) { ::operator delete(p); }
bool operator==(nonassignable_allocator const&) const { return true; }
bool operator!=(nonassignable_allocator const&) const { return false; }
};
template <class T> struct pocx_allocator
{
int x_;
using value_type = T;
using propagate_on_container_copy_assignment = std::true_type;
using propagate_on_container_move_assignment = std::true_type;
using propagate_on_container_swap = std::true_type;
pocx_allocator() : x_{-1} {}
pocx_allocator(pocx_allocator const&) = default;
pocx_allocator(int const x) : x_{x} {}
template <class U>
pocx_allocator(pocx_allocator<U> const& rhs) : x_{rhs.x_}
{
}
pocx_allocator& operator=(pocx_allocator const&) = default;
T* allocate(std::size_t n)
{
return static_cast<T*>(::operator new(n * sizeof(T)));
}
void deallocate(T* p, std::size_t) { ::operator delete(p); }
bool operator==(pocx_allocator const& rhs) const { return x_ == rhs.x_; }
bool operator!=(pocx_allocator const& rhs) const { return x_ != rhs.x_; }
};
template <typename Container, typename Allocator>
struct replace_allocator_impl;
template <typename Container, typename Allocator>
using replace_allocator =
typename replace_allocator_impl<Container, Allocator>::type;
template <
typename K, typename H, typename P, typename A,
template <typename, typename, typename, typename> class Set,
typename Allocator
>
struct replace_allocator_impl<Set<K, H, P, A>, Allocator>
{
using type = Set<
K, H, P, boost::allocator_rebind_t<Allocator, K> >;
};
template <
typename K, typename H, typename T, typename P, typename A,
template <typename, typename, typename, typename, typename> class Map,
typename Allocator
>
struct replace_allocator_impl<Map<K, T, H, P, A>, Allocator>
{
using type = Map<
K, T, H, P,
boost::allocator_rebind_t<Allocator, std::pair<K const, T> > >;
};
template<typename X, typename Allocator>
void node_handle_allocator_tests(
X*, std::pair<Allocator, Allocator> allocators)
{
using value_type = typename X::value_type;
using replaced_allocator_container = replace_allocator<X, Allocator>;
using node_type = typename replaced_allocator_container::node_type;
replaced_allocator_container x1(allocators.first);
node_type nh;
x1.emplace(value_type());
nh = x1.extract(0);
BOOST_TEST(!nh.empty());
BOOST_TEST(nh.get_allocator() == x1.get_allocator());
replaced_allocator_container x2(allocators.second);
x2.emplace(value_type());
nh = x2.extract(0);
BOOST_TEST(!nh.empty());
BOOST_TEST(nh.get_allocator() == x2.get_allocator());
}
template<typename X, typename Allocator>
void node_handle_allocator_swap_tests(
X*, std::pair<Allocator, Allocator> allocators)
{
using value_type = typename X::value_type;
using replaced_allocator_container = replace_allocator<X, Allocator>;
using node_type = typename replaced_allocator_container::node_type;
replaced_allocator_container x1(allocators.first), x2(allocators.second);
x1.emplace(value_type());
x2.emplace(value_type());
node_type nh1, nh2;
nh1 = x1.extract(0);
swap(nh1, nh2);
BOOST_TEST(nh1.empty());
BOOST_TEST(!nh2.empty());
BOOST_TEST(nh2.get_allocator() == x1.get_allocator());
nh1 = x2.extract(0);
swap(nh1, nh2);
BOOST_TEST(!nh1.empty());
BOOST_TEST(nh1.get_allocator() == x1.get_allocator());
BOOST_TEST(!nh2.empty());
BOOST_TEST(nh2.get_allocator() == x2.get_allocator());
}
#if BOOST_WORKAROUND(BOOST_MSVC, <= 1900)
#pragma warning(push)
#pragma warning(disable : 4592) // symbol will be dynamically initialized
#endif
std::pair<
std::allocator<int>, std::allocator<int> > test_std_allocators({},{});
std::pair<
nonassignable_allocator<int>,
nonassignable_allocator<int> > test_nonassignable_allocators({},{});
std::pair<
pocx_allocator<int>, pocx_allocator<int> > test_pocx_allocators(5,6);
#if BOOST_WORKAROUND(BOOST_MSVC, <= 1900)
#pragma warning(pop) // C4592
#endif
#if defined(BOOST_UNORDERED_FOA_TESTS)
boost::unordered_node_map<int, int>* test_map;
boost::unordered_node_set<int>* test_set;
#elif defined(BOOST_UNORDERED_CFOA_TESTS)
boost::concurrent_node_map<int, int>* test_map;
boost::concurrent_node_set<int>* test_set;
#else
boost::unordered_map<int, int>* test_map;
boost::unordered_set<int>* test_set;
#endif
} // namespace
// clang-format off
UNORDERED_TEST(
node_handle_allocator_tests,
((test_map)(test_set))
((test_std_allocators)(test_nonassignable_allocators)
(test_pocx_allocators)))
UNORDERED_TEST(
node_handle_allocator_swap_tests,
((test_map)(test_set))
((test_std_allocators)(test_pocx_allocators)))
// clang-format on
RUN_TESTS()
+12 -1
View File
@@ -33,12 +33,23 @@ namespace pmr_allocator_tests {
test_string_flat_map;
static boost::unordered::pmr::concurrent_flat_map<pmr_string, pmr_string>*
test_pmr_string_flat_map;
static boost::unordered::pmr::concurrent_node_map<std::string, std::string>*
test_string_node_map;
static boost::unordered::pmr::concurrent_node_map<pmr_string, pmr_string>*
test_pmr_string_node_map;
static boost::unordered::pmr::concurrent_flat_set<std::string>*
test_string_flat_set;
static boost::unordered::pmr::concurrent_flat_set<pmr_string>*
test_pmr_string_flat_set;
static boost::unordered::pmr::concurrent_node_set<std::string>*
test_string_node_set;
static boost::unordered::pmr::concurrent_node_set<pmr_string>*
test_pmr_string_node_set;
#define PMR_ALLOCATOR_TESTS_ARGS \
((test_string_flat_map)(test_pmr_string_flat_map)(test_string_flat_set)(test_pmr_string_flat_set))
((test_string_flat_map)(test_pmr_string_flat_map) \
(test_string_node_map)(test_pmr_string_node_map) \
(test_string_flat_set)(test_pmr_string_flat_set) \
(test_string_node_set)(test_pmr_string_node_set))
#elif defined(BOOST_UNORDERED_FOA_TESTS)
static boost::unordered::pmr::unordered_flat_map<std::string, std::string>*
test_string_flat_map;
+37 -15
View File
@@ -1,4 +1,4 @@
// Copyright 2024 Joaquin M Lopez Muoz.
// Copyright 2024 Joaquin M Lopez Munoz.
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@@ -7,8 +7,12 @@
#ifdef BOOST_UNORDERED_CFOA_TESTS
#include <boost/unordered/concurrent_flat_map.hpp>
#include <boost/unordered/concurrent_flat_set.hpp>
#include <boost/unordered/concurrent_node_map.hpp>
#include <boost/unordered/concurrent_node_set.hpp>
#include <boost/unordered/unordered_flat_map.hpp>
#include <boost/unordered/unordered_flat_set.hpp>
#include <boost/unordered/unordered_node_map.hpp>
#include <boost/unordered/unordered_node_set.hpp>
#include "../cfoa/helpers.hpp"
#else
#include "../helpers/unordered.hpp"
@@ -19,6 +23,7 @@
#include "../helpers/test.hpp"
#include <boost/assert.hpp>
#include <boost/core/make_span.hpp>
#include <cmath>
#include <cstring>
template <class T> struct unequal_allocator
@@ -45,16 +50,20 @@ template <class T> struct unequal_allocator
int n_;
};
bool exact_same(double x, double y)
bool esentially_same(double x, double y)
{
return std::memcmp(
reinterpret_cast<void*>(&x), reinterpret_cast<void*>(&y),
sizeof(double))==0;
// Some optimizer-related issues in GCC X86 result in last-bit differences
// on doubles that should otherwise be identical.
// https://stackoverflow.com/a/253874/213114
static constexpr double epsilon = 1.0E-6;
return fabs(x - y) <= ( (fabs(x) > fabs(y) ? fabs(x) : fabs(y)) * epsilon);
}
bool not_exact_same(double x, double y)
bool not_esentially_same(double x, double y)
{
return !exact_same(x, y);
return !esentially_same(x, y);
}
enum check_stats_contition
@@ -69,19 +78,19 @@ void check_stat(const Stats& s, check_stats_contition cond)
{
switch (cond) {
case stats_empty:
BOOST_TEST(exact_same(s.average, 0.0));
BOOST_TEST(exact_same(s.variance, 0.0));
BOOST_TEST(exact_same(s.deviation, 0.0));
BOOST_TEST(esentially_same(s.average, 0.0));
BOOST_TEST(esentially_same(s.variance, 0.0));
BOOST_TEST(esentially_same(s.deviation, 0.0));
break;
case stats_full:
BOOST_TEST_GT(s.average, 0.0);
if(not_exact_same(s.variance, 0.0)) {
if(not_esentially_same(s.variance, 0.0)) {
BOOST_TEST_GT(s.variance, 0.0);
BOOST_TEST_GT(s.deviation, 0.0);
}
break;
case stats_mostly_full:
if(not_exact_same(s.variance, 0.0)) {
if(not_esentially_same(s.variance, 0.0)) {
BOOST_TEST_GT(s.average, 0.0);
BOOST_TEST_GT(s.variance, 0.0);
BOOST_TEST_GT(s.deviation, 0.0);
@@ -94,9 +103,9 @@ void check_stat(const Stats& s, check_stats_contition cond)
template <class Stats> void check_stat(const Stats& s1, const Stats& s2)
{
BOOST_TEST(exact_same(s1.average, s2.average));
BOOST_TEST(exact_same(s1.variance, s2.variance));
BOOST_TEST(exact_same(s1.deviation, s2.deviation));
BOOST_TEST(esentially_same(s1.average, s2.average));
BOOST_TEST(esentially_same(s1.variance, s2.variance));
BOOST_TEST(esentially_same(s1.deviation, s2.deviation));
}
template <class Stats>
@@ -345,15 +354,28 @@ UNORDERED_AUTO_TEST (stats_) {
boost::concurrent_flat_map<
int, int, boost::hash<int>, std::equal_to<int>,
unequal_allocator< std::pair< const int, int> >>>();
test_stats<
boost::concurrent_node_map<
int, int, boost::hash<int>, std::equal_to<int>,
unequal_allocator< std::pair< const int, int> >>>();
test_stats<
boost::concurrent_flat_set<
int, boost::hash<int>, std::equal_to<int>, unequal_allocator<int>>>();
test_stats<
boost::concurrent_node_set<
int, boost::hash<int>, std::equal_to<int>, unequal_allocator<int>>>();
test_stats_concurrent_unordered_interop<
boost::unordered_flat_map<int, int>,
boost::concurrent_flat_map<int, int>>();
test_stats_concurrent_unordered_interop<
boost::unordered_node_map<int, int>,
boost::concurrent_node_map<int, int>>();
test_stats_concurrent_unordered_interop<
boost::unordered_flat_set<int>,
boost::concurrent_flat_set<int>>();
test_stats_concurrent_unordered_interop<
boost::unordered_node_set<int>,
boost::concurrent_node_set<int>>();
#elif defined(BOOST_UNORDERED_FOA_TESTS)
test_stats<
boost::unordered_flat_map<