From ddcab1c171f43835c2c49e33ec2611c28817a263 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 4 Apr 2023 15:16:52 -0700 Subject: [PATCH] Add impl of try_emplace, try_emplace_or_[c]visit --- .../boost/unordered/concurrent_flat_map.hpp | 60 +++ test/Jamfile.v2 | 7 +- test/cfoa/try_emplace_tests.cpp | 402 ++++++++++++++++++ 3 files changed, 468 insertions(+), 1 deletion(-) create mode 100644 test/cfoa/try_emplace_tests.cpp diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 1caf3a6c..bbab9d5c 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -270,6 +270,66 @@ namespace boost { this->insert_or_visit(ilist.begin(), ilist.end(), f); } + template + bool try_emplace(key_type const& k, Args&&... args) + { + return table_.try_emplace(k, std::forward(args)...); + } + + template bool try_emplace(key_type&& k, Args&&... args) + { + return table_.try_emplace(std::move(k), std::forward(args)...); + } + + template + typename std::enable_if< + detail::are_transparent::value, bool>::type + try_emplace(K&& k, Args&&... args) + { + return table_.try_emplace( + std::forward(k), std::forward(args)...); + } + + template + bool try_emplace_or_visit(key_type const& k, F f, Args&&... args) + { + return table_.try_emplace_or_visit(k, f, std::forward(args)...); + } + + template + bool try_emplace_or_cvisit(key_type const& k, F f, Args&&... args) + { + return table_.try_emplace_or_cvisit(k, f, std::forward(args)...); + } + + template + bool try_emplace_or_visit(key_type&& k, F f, Args&&... args) + { + return table_.try_emplace_or_visit( + std::move(k), f, std::forward(args)...); + } + + template + bool try_emplace_or_cvisit(key_type&& k, F f, Args&&... args) + { + return table_.try_emplace_or_cvisit( + std::move(k), f, std::forward(args)...); + } + + template + bool try_emplace_or_visit(K&& k, F f, Args&&... args) + { + return table_.try_emplace_or_visit( + std::forward(k), f, std::forward(args)...); + } + + template + bool try_emplace_or_cvisit(K&& k, F f, Args&&... args) + { + return table_.try_emplace_or_cvisit( + std::forward(k), f, std::forward(args)...); + } + size_type erase(key_type const& k) { return table_.erase(k); } template diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 160e32b2..83e60710 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -214,5 +214,10 @@ rule build_cfoa ( name ) build_cfoa insert_tests ; build_cfoa erase_tests ; +build_cfoa try_emplace_tests ; -alias cfoa_tests : cfoa_insert_tests cfoa_erase_tests ; +alias cfoa_tests : + cfoa_insert_tests + cfoa_erase_tests + cfoa_try_emplace_tests +; diff --git a/test/cfoa/try_emplace_tests.cpp b/test/cfoa/try_emplace_tests.cpp new file mode 100644 index 00000000..1dd39657 --- /dev/null +++ b/test/cfoa/try_emplace_tests.cpp @@ -0,0 +1,402 @@ +// Copyright (C) 2023 Christian Mazakas +// 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.hpp" + +#include + +#include + +namespace { + test::seed_t initialize_seed(511933564); + + struct lvalue_try_emplacer_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + thread_runner(values, [&x, &num_inserts](boost::span s) { + for (auto const& r : s) { + bool b = x.try_emplace(r.first, r.second.x_); + if (b) { + ++num_inserts; + } + } + }); + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + BOOST_TEST_EQ(raii::default_constructor, x.size()); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + } lvalue_try_emplacer; + + struct norehash_lvalue_try_emplacer_type : public lvalue_try_emplacer_type + { + template void operator()(std::vector& values, X& x) + { + x.reserve(values.size()); + lvalue_try_emplacer_type::operator()(values, x); + BOOST_TEST_EQ(raii::move_constructor, 0u); + } + } norehash_lvalue_try_emplacer; + + struct rvalue_try_emplacer_type + { + template void operator()(std::vector& values, X& x) + { + BOOST_TEST_EQ(raii::copy_constructor, 0u); + + std::atomic num_inserts{0}; + thread_runner(values, [&x, &num_inserts](boost::span s) { + for (auto& r : s) { + bool b = x.try_emplace(std::move(r.first), r.second.x_); + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, x.size()); + + if (std::is_same::value) { + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + } else { + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, x.size()); + } + + BOOST_TEST_EQ(raii::default_constructor, x.size()); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + } rvalue_try_emplacer; + + struct norehash_rvalue_try_emplacer_type : public rvalue_try_emplacer_type + { + template void operator()(std::vector& values, X& x) + { + x.reserve(values.size()); + + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_EQ(raii::move_constructor, 0u); + + rvalue_try_emplacer_type::operator()(values, x); + + if (std::is_same::value) { + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + BOOST_TEST_EQ(raii::move_constructor, 0); + } else { + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_EQ(raii::move_constructor, x.size()); + } + } + } norehash_rvalue_try_emplacer; + + struct transp_try_emplace_type + { + template void operator()(std::vector& values, X& x) + { + using is_transparent = + typename boost::make_void::type; + + boost::ignore_unused(); + + BOOST_TEST_EQ(raii::default_constructor, 0u); + + std::atomic num_inserts{0}; + + thread_runner(values, [&x, &num_inserts](boost::span s) { + for (auto& r : s) { + bool b = x.try_emplace(r.first.x_, r.second.x_); + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(raii::default_constructor, 2 * x.size()); + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + } transp_try_emplace; + + struct norehash_transp_try_emplace_type : public transp_try_emplace_type + { + template void operator()(std::vector& values, X& x) + { + x.reserve(values.size()); + transp_try_emplace_type::operator()(values, x); + BOOST_TEST_EQ(raii::move_constructor, 0u); + } + } norehash_transp_try_emplace; + + struct lvalue_try_emplace_or_cvisit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = x.try_emplace_or_cvisit( + r.first, + [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }, + r.second.x_); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, x.size()); + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + // don't check move construction count here because of rehashing + BOOST_TEST_GT(raii::move_constructor, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + } + } lvalue_try_emplace_or_cvisit; + + struct lvalue_try_emplace_or_visit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = x.try_emplace_or_visit( + r.first, + [&num_invokes](typename X::value_type& v) { + (void)v; + ++num_invokes; + }, + r.second.x_); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, x.size()); + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + // don't check move construction count here because of rehashing + BOOST_TEST_GT(raii::move_constructor, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + } + } lvalue_try_emplace_or_visit; + + struct rvalue_try_emplace_or_cvisit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = x.try_emplace_or_cvisit( + std::move(r.first), + [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }, + r.second.x_); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, x.size()); + + if (std::is_same::value) { + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + BOOST_TEST_GE(raii::move_constructor, x.size()); + } else { + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, x.size()); + } + } + } rvalue_try_emplace_or_cvisit; + + struct rvalue_try_emplace_or_visit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = x.try_emplace_or_visit( + std::move(r.first), + [&num_invokes](typename X::value_type& v) { + (void)v; + ++num_invokes; + }, + r.second.x_); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, x.size()); + if (std::is_same::value) { + BOOST_TEST_EQ(raii::copy_constructor, x.size()); + BOOST_TEST_GE(raii::move_constructor, x.size()); + } else { + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, x.size()); + } + } + } rvalue_try_emplace_or_visit; + + struct transp_try_emplace_or_cvisit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = x.try_emplace_or_cvisit( + r.first.x_, + [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }, + r.second.x_); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + BOOST_TEST_EQ(raii::default_constructor, 2 * x.size()); + BOOST_TEST_EQ(raii::copy_constructor, 0u); + } + } transp_try_emplace_or_cvisit; + + struct transp_try_emplace_or_visit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + for (auto& r : s) { + bool b = x.try_emplace_or_visit( + r.first.x_, + [&num_invokes](typename X::value_type& v) { + (void)v; + ++num_invokes; + }, + r.second.x_); + + if (b) { + ++num_inserts; + } + } + }); + + BOOST_TEST_EQ(num_inserts, x.size()); + BOOST_TEST_EQ(num_invokes, values.size() - x.size()); + + BOOST_TEST_EQ(raii::default_constructor, 2 * x.size()); + BOOST_TEST_EQ(raii::copy_constructor, 0u); + } + } transp_try_emplace_or_visit; + + template + void try_emplace(X*, G gen, F try_emplacer, test::random_generator rg) + { + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + raii::reset_counts(); + + { + X x; + + try_emplacer(values, x); + + BOOST_TEST_EQ(x.size(), reference_map.size()); + + using value_type = typename X::value_type; + BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + if (rg == test::sequential) { + BOOST_TEST_EQ(kv.second, reference_map[kv.first]); + } + })); + } + + BOOST_TEST_GE(raii::default_constructor, 0u); + BOOST_TEST_GE(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, 0u); + BOOST_TEST_GT(raii::destructor, 0u); + + BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor + + raii::move_constructor, + raii::destructor); + } + + boost::unordered::concurrent_flat_map* map; + boost::unordered::concurrent_flat_map* transp_map; + +} // namespace + +using test::default_generator; +using test::limited_range; +using test::sequential; + +// clang-format off +UNORDERED_TEST( + try_emplace, + ((map)) + ((value_type_generator)(init_type_generator)) + ((lvalue_try_emplacer)(norehash_lvalue_try_emplacer) + (rvalue_try_emplacer)(norehash_rvalue_try_emplacer) + (lvalue_try_emplace_or_cvisit)(lvalue_try_emplace_or_visit) + (rvalue_try_emplace_or_cvisit)(rvalue_try_emplace_or_visit)) + ((default_generator)(sequential)(limited_range))) + +UNORDERED_TEST( + try_emplace, + ((transp_map)) + ((init_type_generator)) + ((transp_try_emplace)(norehash_transp_try_emplace) + (transp_try_emplace_or_cvisit)(transp_try_emplace_or_visit)) + ((default_generator)(sequential)(limited_range))) +// clang-format on + +RUN_TESTS()