Optimize emplace() for exactly a value_type or init_type argument (#227)

* Add structs to count special member functions

* Add failing emplace tests, that will pass after making the optimization

* Optimize emplace() to not allocate when we already have a value_type or init_type

* Fix newly failing cfoa tests
This commit is contained in:
Braden Ganetsky
2023-12-31 11:56:14 -06:00
committed by GitHub
parent 75a94781d5
commit 67c5cdb3a6
10 changed files with 418 additions and 37 deletions

View File

@ -28,6 +28,7 @@
#include <boost/unordered/detail/foa/tuple_rotate_right.hpp>
#include <boost/unordered/detail/serialization_version.hpp>
#include <boost/unordered/detail/static_assert.hpp>
#include <boost/unordered/detail/type_traits.hpp>
#include <cstddef>
#include <functional>
#include <initializer_list>
@ -682,6 +683,14 @@ public:
return construct_and_emplace(std::forward<Args>(args)...);
}
/* Optimization for value_type and init_type, to avoid constructing twice */
template<typename Value>
BOOST_FORCEINLINE auto emplace(Value&& x)->typename std::enable_if<
detail::is_similar_to_any<Value,value_type,init_type>::value,bool>::type
{
return emplace_impl(std::forward<Value>(x));
}
BOOST_FORCEINLINE bool
insert(const init_type& x){return emplace_impl(x);}

View File

@ -18,6 +18,7 @@
#include <boost/core/serialization.hpp>
#include <boost/unordered/detail/foa/core.hpp>
#include <boost/unordered/detail/serialize_tracked_address.hpp>
#include <boost/unordered/detail/type_traits.hpp>
#include <cstddef>
#include <iterator>
#include <memory>
@ -405,6 +406,16 @@ public:
return emplace_impl(type_policy::move(x.value()));
}
/* Optimization for value_type and init_type, to avoid constructing twice */
template <typename T>
BOOST_FORCEINLINE typename std::enable_if<
detail::is_similar_to_any<T, value_type, init_type>::value,
std::pair<iterator, bool> >::type
emplace(T&& x)
{
return emplace_impl(std::forward<T>(x));
}
template<typename Key,typename... Args>
BOOST_FORCEINLINE std::pair<iterator,bool> try_emplace(
Key&& x,Args&&... args)

View File

@ -147,6 +147,23 @@ namespace boost {
!std::is_convertible<Key, const_iterator>::value;
};
template <class T>
using remove_cvref_t =
typename std::remove_cv<typename std::remove_reference<T>::type>::type;
template <class T, class U>
using is_similar = std::is_same<remove_cvref_t<T>, remove_cvref_t<U> >;
template <class, class...> struct is_similar_to_any : std::false_type
{
};
template <class T, class U, class... Us>
struct is_similar_to_any<T, U, Us...>
: std::conditional<is_similar<T, U>::value, is_similar<T, U>,
is_similar_to_any<T, Us...> >::type
{
};
#if BOOST_UNORDERED_TEMPLATE_DEDUCTION_GUIDES
// https://eel.is/c++draft/container.requirements#container.alloc.reqmts-34
// https://eel.is/c++draft/container.requirements#unord.req.general-243

View File

@ -85,6 +85,7 @@ local FCA_TESTS =
contains_tests
copy_tests
deduction_tests
emplace_smf_tests
emplace_tests
equality_tests
equivalent_keys_tests
@ -201,6 +202,7 @@ local FOA_TESTS =
assign_tests
insert_tests
insert_hint_tests
emplace_smf_tests
emplace_tests
erase_tests
merge_tests

View File

@ -968,11 +968,9 @@ namespace {
BOOST_TEST_EQ(x.key_eq(), key_equal(2));
BOOST_TEST_EQ(
raii::copy_constructor, value_type_cardinality * values.size());
BOOST_TEST_EQ(
raii::destructor, value_type_cardinality * values.size());
BOOST_TEST_EQ(
raii::move_constructor, value_type_cardinality * reference_cont.size());
raii::copy_constructor, value_type_cardinality * reference_cont.size());
BOOST_TEST_EQ(raii::destructor, 0u);
BOOST_TEST_EQ(raii::move_constructor, 0u);
BOOST_TEST_EQ(raii::copy_assignment, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
}
@ -998,11 +996,9 @@ namespace {
BOOST_TEST_EQ(flat.key_eq(), key_equal(2));
BOOST_TEST_EQ(
raii::copy_constructor, value_type_cardinality * values.size());
BOOST_TEST_EQ(
raii::destructor, value_type_cardinality * values.size());
BOOST_TEST_EQ(
raii::move_constructor, value_type_cardinality * reference_cont.size());
raii::copy_constructor, value_type_cardinality * reference_cont.size());
BOOST_TEST_EQ(raii::destructor, 0u);
BOOST_TEST_EQ(raii::move_constructor, 0u);
BOOST_TEST_EQ(raii::copy_assignment, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
}
@ -1030,14 +1026,11 @@ namespace {
BOOST_TEST_EQ(x.key_eq(), key_equal(2));
BOOST_TEST_EQ(
raii::copy_constructor, value_type_cardinality * values.size());
raii::copy_constructor, value_type_cardinality * reference_cont.size());
BOOST_TEST_EQ(
raii::destructor,
value_type_cardinality * values.size() +
value_type_cardinality * reference_cont.size());
raii::destructor, value_type_cardinality * reference_cont.size());
BOOST_TEST_EQ(
raii::move_constructor,
2 * value_type_cardinality * reference_cont.size());
raii::move_constructor, value_type_cardinality * reference_cont.size());
BOOST_TEST_EQ(raii::copy_assignment, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
}
@ -1063,14 +1056,11 @@ namespace {
BOOST_TEST_EQ(flat.key_eq(), key_equal(2));
BOOST_TEST_EQ(
raii::copy_constructor, value_type_cardinality * values.size());
raii::copy_constructor, value_type_cardinality * reference_cont.size());
BOOST_TEST_EQ(
raii::destructor,
value_type_cardinality * values.size() +
value_type_cardinality * reference_cont.size());
raii::destructor, value_type_cardinality * reference_cont.size());
BOOST_TEST_EQ(
raii::move_constructor,
2 * value_type_cardinality * reference_cont.size());
raii::move_constructor, value_type_cardinality * reference_cont.size());
BOOST_TEST_EQ(raii::copy_assignment, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
}

View File

@ -712,9 +712,9 @@ namespace {
BOOST_TEST_EQ(raii::default_constructor, 0u);
BOOST_TEST_EQ(
raii::copy_constructor, value_type_cardinality * init_list.size());
raii::copy_constructor, value_type_cardinality * init_list.size() / 2u);
BOOST_TEST_EQ(
raii::move_constructor, value_type_cardinality * 11u);
raii::move_constructor, 0u);
}
check_raii_counts();
@ -730,9 +730,9 @@ namespace {
BOOST_TEST_EQ(raii::default_constructor, 0u);
BOOST_TEST_EQ(
raii::copy_constructor, value_type_cardinality * init_list.size());
raii::copy_constructor, value_type_cardinality * init_list.size() / 2u);
BOOST_TEST_EQ(
raii::move_constructor, value_type_cardinality * 11u);
raii::move_constructor, 0u);
}
check_raii_counts();
@ -748,9 +748,9 @@ namespace {
BOOST_TEST_EQ(raii::default_constructor, 0u);
BOOST_TEST_EQ(
raii::copy_constructor, value_type_cardinality * init_list.size());
raii::copy_constructor, value_type_cardinality * init_list.size() / 2u);
BOOST_TEST_EQ(
raii::move_constructor, value_type_cardinality * 11u);
raii::move_constructor, 0u);
}
check_raii_counts();
@ -766,9 +766,9 @@ namespace {
BOOST_TEST_EQ(raii::default_constructor, 0u);
BOOST_TEST_EQ(
raii::copy_constructor, value_type_cardinality * init_list.size());
raii::copy_constructor, value_type_cardinality * init_list.size() / 2u);
BOOST_TEST_EQ(
raii::move_constructor, value_type_cardinality * 11u);
raii::move_constructor, 0u);
}
check_raii_counts();
}
@ -867,6 +867,9 @@ namespace {
template <class X, class GF>
void flat_constructor(X*, GF gen_factory, test::random_generator rg)
{
using value_type = typename X::value_type;
static constexpr auto value_type_cardinality =
value_cardinality<value_type>::value;
using allocator_type = typename X::allocator_type;
auto gen = gen_factory.template get<X>();
@ -886,8 +889,8 @@ namespace {
auto const old_cc = +raii::copy_constructor;
BOOST_TEST_EQ(old_dc, 0u);
BOOST_TEST_GT(old_mc, 0u);
BOOST_TEST_GT(old_cc, 0u);
BOOST_TEST_EQ(old_mc, 0u);
BOOST_TEST_EQ(old_cc, value_type_cardinality * flat.size());
X x(std::move(flat));
@ -931,8 +934,8 @@ namespace {
auto const old_cc = +raii::copy_constructor;
BOOST_TEST_EQ(old_dc, 0u);
BOOST_TEST_GT(old_mc, 0u);
BOOST_TEST_GT(old_cc, 0u);
BOOST_TEST_EQ(old_mc, 0u);
BOOST_TEST_EQ(old_cc, 2u * value_type_cardinality * x.size());
flat_container<X> flat(std::move(x));

View File

@ -170,6 +170,74 @@ namespace {
}
} lvalue_emplace_or_visit;
struct copy_emplacer_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
static constexpr auto value_type_cardinality =
value_cardinality<typename X::value_type>::value;
std::atomic<std::uint64_t> num_inserts{0};
thread_runner(values, [&x, &num_inserts](boost::span<T> s) {
for (auto const& r : s) {
bool b = x.emplace(r);
if (b) {
++num_inserts;
}
}
});
BOOST_TEST_EQ(num_inserts, x.size());
BOOST_TEST_EQ(raii::default_constructor, 0u);
BOOST_TEST_EQ(raii::copy_constructor, value_type_cardinality * x.size());
BOOST_TEST_GT(raii::move_constructor, 0u);
BOOST_TEST_EQ(raii::copy_assignment, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
}
} copy_emplacer;
struct move_emplacer_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
static constexpr auto value_type_cardinality =
value_cardinality<typename X::value_type>::value;
std::atomic<std::uint64_t> num_inserts{0};
thread_runner(values, [&x, &num_inserts](boost::span<T> s) {
for (auto& r : s) {
bool b = x.emplace(std::move(r));
if (b) {
++num_inserts;
}
}
});
BOOST_TEST_EQ(num_inserts, x.size());
BOOST_TEST_EQ(raii::default_constructor, 0u);
#if defined(BOOST_MSVC)
#pragma warning(push)
#pragma warning(disable : 4127) // conditional expression is constant
#endif
if (std::is_same<T, typename X::value_type>::value &&
!std::is_same<typename X::key_type,
typename X::value_type>::value) { // map value_type can only be
// copied, no move
BOOST_TEST_EQ(raii::copy_constructor, x.size());
} else {
BOOST_TEST_EQ(raii::copy_constructor, 0u);
}
#if defined(BOOST_MSVC)
#pragma warning(pop) // C4127
#endif
BOOST_TEST_GT(raii::move_constructor, value_type_cardinality * x.size());
BOOST_TEST_EQ(raii::copy_assignment, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
}
} move_emplacer;
template <class X, class GF, class F>
void emplace(X*, GF gen_factory, F emplacer, test::random_generator rg)
{
@ -220,7 +288,7 @@ UNORDERED_TEST(
((map)(set))
((value_type_generator_factory)(init_type_generator_factory))
((lvalue_emplacer)(norehash_lvalue_emplacer)
(lvalue_emplace_or_cvisit)(lvalue_emplace_or_visit))
(lvalue_emplace_or_cvisit)(lvalue_emplace_or_visit)(copy_emplacer)(move_emplacer))
((default_generator)(sequential)(limited_range)))
// clang-format on

View File

@ -33,7 +33,7 @@ static std::size_t const num_threads =
std::atomic_bool should_throw{false};
constexpr std::uint32_t throw_threshold = 2500;
constexpr std::uint32_t throw_threshold = 2300;
constexpr std::uint32_t alloc_throw_threshold = 10;
void enable_exceptions() { should_throw = true; }

View File

@ -7,6 +7,7 @@
#define BOOST_UNORDERED_TEST_HELPERS_COUNT_HEAD
#include <boost/core/lightweight_test.hpp>
#include <boost/container_hash/hash.hpp>
namespace test {
struct object_count
@ -84,6 +85,111 @@ namespace test {
return global_object_count.constructions - constructions_;
}
};
}
struct smf_count
{
int default_constructions = 0;
int copy_constructions = 0;
int move_constructions = 0;
int copy_assignments = 0;
int move_assignments = 0;
int destructions = 0;
#if (BOOST_CXX_VERSION < 201402L) || (defined(_MSC_VER) && _MSC_VER < 1910)
smf_count() = default;
smf_count(int default_constructions_, int copy_constructions_,
int move_constructions_, int copy_assignments_, int move_assignments_,
int destructions_)
: default_constructions(default_constructions_),
copy_constructions(copy_constructions_),
move_constructions(move_constructions_),
copy_assignments(copy_assignments_),
move_assignments(move_assignments_), destructions(destructions_)
{
}
#endif
void reset() { *this = smf_count(); }
void default_construct() { ++default_constructions; }
void copy_construct() { ++copy_constructions; }
void move_construct() { ++move_constructions; }
void copy_assign() { ++copy_assignments; }
void move_assign() { ++move_assignments; }
void destruct() { ++destructions; }
friend bool operator==(smf_count const& lhs, smf_count const& rhs)
{
return lhs.default_constructions == rhs.default_constructions &&
lhs.copy_constructions == rhs.copy_constructions &&
lhs.move_constructions == rhs.move_constructions &&
lhs.copy_assignments == rhs.copy_assignments &&
lhs.move_assignments == rhs.move_assignments &&
lhs.destructions == rhs.destructions;
}
friend std::ostream& operator<<(std::ostream& out, smf_count const& c)
{
out << "[default_constructions: " << c.default_constructions
<< ", copy_constructions: " << c.copy_constructions
<< ", move_constructions: " << c.move_constructions
<< ", copy_assignments: " << c.copy_assignments
<< ", move_assignments: " << c.move_assignments
<< ", destructions: " << c.destructions << "]";
return out;
}
};
template <class Tag> class smf_counted_object
{
public:
static smf_count count;
static void reset_count() { count.reset(); }
smf_counted_object() : index_(++running_index)
{
count.default_construct();
}
smf_counted_object(smf_counted_object const& rhs) : index_(rhs.index_)
{
count.copy_construct();
}
smf_counted_object(smf_counted_object&& rhs) noexcept : index_(rhs.index_)
{
count.move_construct();
}
smf_counted_object& operator=(smf_counted_object const& rhs)
{
count.copy_assign();
index_ = rhs.index_;
return *this;
}
smf_counted_object& operator=(smf_counted_object&& rhs) noexcept
{
count.move_assign();
index_ = rhs.index_;
return *this;
}
~smf_counted_object() { count.destruct(); }
friend bool operator==(
smf_counted_object const& lhs, smf_counted_object const& rhs)
{
return lhs.index_ == rhs.index_;
}
friend std::size_t hash_value(smf_counted_object const& x)
{
return boost::hash<int>()(x.index_);
}
private:
static int running_index;
int index_;
};
template <class Tag> smf_count smf_counted_object<Tag>::count = {};
template <class Tag> int smf_counted_object<Tag>::running_index = 0;
} // namespace test
#endif

View File

@ -0,0 +1,175 @@
//
// Copyright 2023 Braden Ganetsky.
// 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 "../helpers/unordered.hpp"
#include "../helpers/count.hpp"
#include "../helpers/test.hpp"
namespace emplace_smf_tests {
using test::smf_count;
using test::smf_counted_object;
using counted_key = smf_counted_object<struct key_tag_>;
using counted_value = smf_counted_object<struct value_tag_>;
void reset_counts()
{
counted_key::reset_count();
counted_value::reset_count();
}
#ifdef BOOST_UNORDERED_FOA_TESTS
static boost::unordered_flat_map<counted_key, counted_value>* test_smf_map;
static boost::unordered_node_map<counted_key, counted_value>*
test_smf_node_map;
static boost::unordered_flat_set<counted_value>* test_smf_set;
static boost::unordered_node_set<counted_value>* test_smf_node_set;
#define EMPLACE_SMF_TESTS_MAP_ARGS ((test_smf_map)(test_smf_node_map))
#define EMPLACE_SMF_TESTS_SET_ARGS ((test_smf_set)(test_smf_node_set))
#else
static boost::unordered_map<counted_key, counted_value>* test_smf_map;
static boost::unordered_set<counted_value>* test_smf_set;
#define EMPLACE_SMF_TESTS_MAP_ARGS ((test_smf_map))
#define EMPLACE_SMF_TESTS_SET_ARGS ((test_smf_set))
#endif
template <class X> static void emplace_smf_value_type_map(X*)
{
using container = X;
using value_type = typename container::value_type;
container x;
{
value_type val{counted_key{}, counted_value{}};
x.clear();
reset_counts();
x.emplace(val);
BOOST_TEST_EQ(counted_key::count, (smf_count{0, 1, 0, 0, 0, 0}));
BOOST_TEST_EQ(counted_value::count, (smf_count{0, 1, 0, 0, 0, 0}));
}
{
value_type val{counted_key{}, counted_value{}};
x.clear();
reset_counts();
x.emplace(std::move(val));
BOOST_TEST_EQ(counted_key::count, (smf_count{0, 1, 0, 0, 0, 0}));
BOOST_TEST_EQ(counted_value::count, (smf_count{0, 0, 1, 0, 0, 0}));
}
{
x.clear();
reset_counts();
x.emplace(value_type{counted_key{}, counted_value{}});
BOOST_TEST_EQ(counted_key::count, (smf_count{1, 1, 1, 0, 0, 2}));
BOOST_TEST_EQ(counted_value::count, (smf_count{1, 0, 2, 0, 0, 2}));
}
{
counted_key key{};
counted_value value{};
x.clear();
reset_counts();
x.emplace(value_type{std::move(key), std::move(value)});
BOOST_TEST_EQ(counted_key::count, (smf_count{0, 1, 1, 0, 0, 1}));
BOOST_TEST_EQ(counted_value::count, (smf_count{0, 0, 2, 0, 0, 1}));
}
}
UNORDERED_TEST(emplace_smf_value_type_map, EMPLACE_SMF_TESTS_MAP_ARGS)
template <class X> static void emplace_smf_init_type_map(X*)
{
using container = X;
#ifdef BOOST_UNORDERED_FOA_TESTS
using init_type = typename container::init_type;
#else
using raw_key =
typename std::remove_const<typename container::key_type>::type;
using init_type = std::pair<raw_key, typename container::mapped_type>;
#endif
container x;
{
init_type val{counted_key{}, counted_value{}};
x.clear();
reset_counts();
x.emplace(val);
BOOST_TEST_EQ(counted_key::count, (smf_count{0, 1, 0, 0, 0, 0}));
BOOST_TEST_EQ(counted_value::count, (smf_count{0, 1, 0, 0, 0, 0}));
}
{
init_type val{counted_key{}, counted_value{}};
x.clear();
reset_counts();
x.emplace(std::move(val));
BOOST_TEST_EQ(counted_key::count, (smf_count{0, 0, 1, 0, 0, 0}));
BOOST_TEST_EQ(counted_value::count, (smf_count{0, 0, 1, 0, 0, 0}));
}
{
x.clear();
reset_counts();
x.emplace(init_type{counted_key{}, counted_value{}});
BOOST_TEST_EQ(counted_key::count, (smf_count{1, 0, 2, 0, 0, 2}));
BOOST_TEST_EQ(counted_value::count, (smf_count{1, 0, 2, 0, 0, 2}));
}
{
counted_key key{};
counted_value value{};
x.clear();
reset_counts();
x.emplace(init_type{std::move(key), std::move(value)});
BOOST_TEST_EQ(counted_key::count, (smf_count{0, 0, 2, 0, 0, 1}));
BOOST_TEST_EQ(counted_value::count, (smf_count{0, 0, 2, 0, 0, 1}));
}
}
UNORDERED_TEST(emplace_smf_init_type_map, EMPLACE_SMF_TESTS_MAP_ARGS)
template <class X> static void emplace_smf_value_type_set(X*)
{
using container = X;
using value_type = typename container::value_type;
#ifdef BOOST_UNORDERED_FOA_TESTS
BOOST_STATIC_ASSERT(
std::is_same<value_type, typename container::init_type>::value);
#endif
BOOST_STATIC_ASSERT(std::is_same<value_type, counted_value>::value);
container x;
{
counted_value val{};
x.clear();
reset_counts();
x.emplace(val);
BOOST_TEST_EQ(counted_value::count, (smf_count{0, 1, 0, 0, 0, 0}));
}
{
counted_value val{};
x.clear();
reset_counts();
x.emplace(std::move(val));
BOOST_TEST_EQ(counted_value::count, (smf_count{0, 0, 1, 0, 0, 0}));
}
{
x.clear();
reset_counts();
x.emplace(counted_value{});
BOOST_TEST_EQ(counted_value::count, (smf_count{1, 0, 1, 0, 0, 1}));
}
}
UNORDERED_TEST(emplace_smf_value_type_set, EMPLACE_SMF_TESTS_SET_ARGS)
} // namespace emplace_smf_tests
RUN_TESTS()