From bee4ed2e5fb0d18e40daa038ec7a00f8d51fded1 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 21 Jul 2023 18:16:28 +0200 Subject: [PATCH 01/18] implemented [c]visit_(until|while) --- .../boost/unordered/concurrent_flat_map.hpp | 100 ++++++++++ .../unordered/detail/foa/concurrent_table.hpp | 171 ++++++++++++++++-- 2 files changed, 258 insertions(+), 13 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 181c8987..10712c9e 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -355,6 +355,106 @@ namespace boost { } #endif + template bool visit_until(F f) + { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) + return table_.visit_until(f); + } + + template bool visit_until(F f) const + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + return table_.visit_until(f); + } + + template bool cvisit_until(F f) const + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + return table_.cvisit_until(f); + } + +#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) + template + typename std::enable_if::value, + bool>::type + visit_until(ExecPolicy&& p, F f) + { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) + BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy) + return table_.visit_until(p, f); + } + + template + typename std::enable_if::value, + bool>::type + visit_until(ExecPolicy&& p, F f) const + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy) + return table_.visit_until(p, f); + } + + template + typename std::enable_if::value, + bool>::type + cvisit_until(ExecPolicy&& p, F f) const + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy) + return table_.cvisit_until(p, f); + } +#endif + + template bool visit_while(F f) + { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) + return table_.visit_while(f); + } + + template bool visit_while(F f) const + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + return table_.visit_while(f); + } + + template bool cvisit_while(F f) const + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + return table_.cvisit_while(f); + } + +#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) + template + typename std::enable_if::value, + bool>::type + visit_while(ExecPolicy&& p, F f) + { + BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) + BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy) + return table_.visit_while(p, f); + } + + template + typename std::enable_if::value, + bool>::type + visit_while(ExecPolicy&& p, F f) const + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy) + return table_.visit_while(p, f); + } + + template + typename std::enable_if::value, + bool>::type + cvisit_while(ExecPolicy&& p, F f) const + { + BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) + BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy) + return table_.cvisit_while(p, f); + } +#endif + /// Modifiers /// diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 0f1f1145..5a28f782 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -539,6 +539,86 @@ public: } #endif + template bool visit_until(F&& f) + { + return !visit_while([&](value_type& x){return !f(x);}); + } + + template bool visit_until(F&& f)const + { + return !visit_while([&](const value_type& x){return !f(x);}); + } + + template bool cvisit_until(F&& f)const + { + return visit_while(std::forward(f)); + } + +#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) + template + bool visit_until(ExecutionPolicy&& policy,F&& f) + { + return !visit_while( + std::forward(policy), + [&](value_type& x){return !f(x);}); + } + + template + bool visit_until(ExecutionPolicy&& policy,F&& f)const + { + return !visit_while( + std::forward(policy), + [&](const value_type& x){return !f(x);}); + } + + template + bool cvisit_until(ExecutionPolicy&& policy,F&& f)const + { + return visit_until( + std::forward(policy),std::forward(f)); + } +#endif + + template bool visit_while(F&& f) + { + return visit_while_impl(group_exclusive{},std::forward(f)); + } + + template bool visit_while(F&& f)const + { + return visit_while_impl(group_shared{},std::forward(f)); + } + + template bool cvisit_while(F&& f)const + { + return visit_while(std::forward(f)); + } + +#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) + template + bool visit_while(ExecutionPolicy&& policy,F&& f) + { + return visit_while_impl( + group_exclusive{}, + std::forward(policy),std::forward(f)); + } + + template + bool visit_while(ExecutionPolicy&& policy,F&& f)const + { + return visit_while_impl( + group_shared{}, + std::forward(policy),std::forward(f)); + } + + template + bool cvisit_while(ExecutionPolicy&& policy,F&& f)const + { + return visit_while( + std::forward(policy),std::forward(f)); + } +#endif + bool empty()const noexcept{return size()==0;} std::size_t size()const noexcept @@ -970,6 +1050,29 @@ private: } #endif + template + bool visit_while_impl(GroupAccessMode access_mode,F&& f)const + { + auto lck=shared_access(); + return for_all_elements_while(access_mode,[&](element_type* p){ + return f(cast_for(access_mode,type_policy::value_from(*p))); + }); + } + +#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) + template + bool visit_while_impl( + GroupAccessMode access_mode,ExecutionPolicy&& policy,F&& f)const + { + auto lck=shared_access(); + return for_all_elements_while( + access_mode,std::forward(policy), + [&](element_type* p){ + return f(cast_for(access_mode,type_policy::value_from(*p))); + }); + } +#endif + template BOOST_FORCEINLINE std::size_t unprotected_visit( GroupAccessMode access_mode, @@ -1253,19 +1356,38 @@ private: template auto for_all_elements(GroupAccessMode access_mode,F f)const ->decltype(f(nullptr,0,nullptr),void()) + { + for_all_elements_while( + access_mode,[&](group_type* pg,unsigned int n,element_type* p) + {f(pg,n,p);return true;}); + } + + template + auto for_all_elements_while(GroupAccessMode access_mode,F f)const + ->decltype(f(nullptr),bool()) + { + return for_all_elements_while( + access_mode,[&](group_type*,unsigned int,element_type* p){return f(p);}); + } + + template + auto for_all_elements_while(GroupAccessMode access_mode,F f)const + ->decltype(f(nullptr,0,nullptr),bool()) { auto p=this->arrays.elements; - if(!p)return; - for(auto pg=this->arrays.groups,last=pg+this->arrays.groups_size_mask+1; - pg!=last;++pg,p+=N){ - auto lck=access(access_mode,(std::size_t)(pg-this->arrays.groups)); - auto mask=this->match_really_occupied(pg,last); - while(mask){ - auto n=unchecked_countr_zero(mask); - f(pg,n,p+n); - mask&=mask-1; + if(p){ + for(auto pg=this->arrays.groups,last=pg+this->arrays.groups_size_mask+1; + pg!=last;++pg,p+=N){ + auto lck=access(access_mode,(std::size_t)(pg-this->arrays.groups)); + auto mask=this->match_really_occupied(pg,last); + while(mask){ + auto n=unchecked_countr_zero(mask); + if(!f(pg,n,p+n))return false; + mask&=mask-1; + } } } + return true; } #if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) @@ -1289,10 +1411,10 @@ private: last=first+this->arrays.groups_size_mask+1; std::for_each(std::forward(policy),first,last, [&,this](group_type& g){ - std::size_t pos=static_cast(&g-first); - auto p=this->arrays.elements+pos*N; - auto lck=access(access_mode,pos); - auto mask=this->match_really_occupied(&g,last); + auto pos=static_cast(&g-first); + auto p=this->arrays.elements+pos*N; + auto lck=access(access_mode,pos); + auto mask=this->match_really_occupied(&g,last); while(mask){ auto n=unchecked_countr_zero(mask); f(&g,n,p+n); @@ -1301,6 +1423,29 @@ private: } ); } + + template + bool for_all_elements_while( + GroupAccessMode access_mode,ExecutionPolicy&& policy,F f)const + { + if(!this->arrays.elements)return true; + auto first=this->arrays.groups, + last=first+this->arrays.groups_size_mask+1; + return std::all_of(std::forward(policy),first,last, + [&,this](group_type& g){ + auto pos=static_cast(&g-first); + auto p=this->arrays.elements+pos*N; + auto lck=access(access_mode,pos); + auto mask=this->match_really_occupied(&g,last); + while(mask){ + auto n=unchecked_countr_zero(mask); + if(!f(p+n))return false; + mask&=mask-1; + } + return true; + } + ); + } #endif static std::atomic thread_counter; From 5a4d93a72d44cdafb1eb4de63d2963ecc1b31eb2 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Fri, 21 Jul 2023 19:39:43 +0200 Subject: [PATCH 02/18] documented [c]visit_(until|while) --- doc/unordered/changes.adoc | 5 ++ doc/unordered/concurrent.adoc | 33 +++++++- doc/unordered/concurrent_flat_map.adoc | 108 +++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 3 deletions(-) diff --git a/doc/unordered/changes.adoc b/doc/unordered/changes.adoc index 5679a9ea..6cf4c767 100644 --- a/doc/unordered/changes.adoc +++ b/doc/unordered/changes.adoc @@ -6,6 +6,11 @@ :github-pr-url: https://github.com/boostorg/unordered/pull :cpp: C++ +== Release 1.84.0 + +* Added `[c]visit_until` and `[c]visit_while` operations to `boost::concurrent_map`, +with serial and parallel variants. + == Release 1.83.0 - Major update * Added `boost::concurrent_flat_map`, a fast, thread-safe hashmap based on open addressing. diff --git a/doc/unordered/concurrent.adoc b/doc/unordered/concurrent.adoc index 3410570a..1d66a53c 100644 --- a/doc/unordered/concurrent.adoc +++ b/doc/unordered/concurrent.adoc @@ -154,7 +154,34 @@ m.visit_all(std::execution::par, [](auto& x) { // run in parallel }); ---- -There is another whole-table visitation operation, `erase_if`: +Traversal can be interrupted midway: + +[source,c++] +---- +// finds the key to a given (unique value) + +int key = 0; +int value = ...; +bool found = m.visit_until([&](const auto& x) { + if(x.second == value) { + key = x.first; + return true; // finish + } + else { + return false; // keep on visiting + } +}); + +if(found) { ... } + +// check if all values are != 0 + +bool all_ok = m.visit_while([&](const auto& x) { + return x.second != 0; +}); +---- + +There is one last whole-table visitation operation, `erase_if`: [source,c++] ---- @@ -163,8 +190,8 @@ m.erase_if([](auto& x) { }); ---- -`erase_if` can also be parallelized. Note that, in order to increase efficiency, -these operations do not block the table during execution: this implies that elements +`visit_until`, `visit_while` and `erase_if` can also be parallelized. Note that, in order to increase efficiency, +whole-table visitation operations do not block the table during execution: this implies that elements may be inserted, modified or erased by other threads during visitation. It is advisable not to assume too much about the exact global state of a `boost::concurrent_flat_map` at any point in your program. diff --git a/doc/unordered/concurrent_flat_map.adoc b/doc/unordered/concurrent_flat_map.adoc index fcdc8662..dd9199b7 100644 --- a/doc/unordered/concurrent_flat_map.adoc +++ b/doc/unordered/concurrent_flat_map.adoc @@ -114,6 +114,26 @@ namespace boost { template void xref:#concurrent_flat_map_parallel_cvisit_all[cvisit_all](ExecutionPolicy&& policy, F f) const; + template bool xref:#concurrent_flat_map_cvisit_until[visit_until](F f); + template bool xref:#concurrent_flat_map_cvisit_until[visit_until](F f) const; + template bool xref:#concurrent_flat_map_cvisit_until[cvisit_until](F f) const; + template + bool xref:#concurrent_flat_map_parallel_cvisit_until[visit_until](ExecutionPolicy&& policy, F f); + template + bool xref:#concurrent_flat_map_parallel_cvisit_until[visit_until](ExecutionPolicy&& policy, F f) const; + template + bool xref:#concurrent_flat_map_parallel_cvisit_until[cvisit_until](ExecutionPolicy&& policy, F f) const; + + template bool xref:#concurrent_flat_map_cvisit_while[visit_while](F f); + template bool xref:#concurrent_flat_map_cvisit_while[visit_while](F f) const; + template bool xref:#concurrent_flat_map_cvisit_while[cvisit_while](F f) const; + template + bool xref:#concurrent_flat_map_parallel_cvisit_while[visit_while](ExecutionPolicy&& policy, F f); + template + bool xref:#concurrent_flat_map_parallel_cvisit_while[visit_while](ExecutionPolicy&& policy, F f) const; + template + bool xref:#concurrent_flat_map_parallel_cvisit_while[cvisit_while](ExecutionPolicy&& policy, F f) const; + // capacity ++[[nodiscard]]++ bool xref:#concurrent_flat_map_empty[empty]() const noexcept; size_type xref:#concurrent_flat_map_size[size]() const noexcept; @@ -720,6 +740,94 @@ Unsequenced execution policies are not allowed. --- +==== [c]visit_until + +```c++ +template bool visit_until(F f); +template bool visit_until(F f) const; +template bool cvisit_until(F f) const; +``` + +Successively invokes `f` with references to each of the elements in the table until `f` returns `true` +or all the elements are visited. +Such references to the elements are const iff `*this` is const. + +[horizontal] +Returns:;; `true` iff `f` ever returns `true`. + +--- + +==== Parallel [c]visit_until + +```c++ +template bool visit_until(ExecutionPolicy&& policy, F f); +template bool visit_until(ExecutionPolicy&& policy, F f) const; +template bool cvisit_until(ExecutionPolicy&& policy, F f) const; +``` + +Invokes `f` with references to each of the elements in the table until `f` returns `true` +or all the elements are visited. +Such references to the elements are const iff `*this` is const. +Execution is parallelized according to the semantics of the execution policy specified. + +[horizontal] +Returns:;; `true` iff `f` ever returns `true`. +Throws:;; Depending on the exception handling mechanism of the execution policy used, may call `std::terminate` if an exception is thrown within `f`. +Notes:;; Only available in compilers supporting C++17 parallel algorithms. + ++ +These overloads only participate in overload resolution if `std::is_execution_policy_v>` is `true`. + ++ +Unsequenced execution policies are not allowed. + ++ +Parallelization implies that execution does not necessary finish as soon as `f` returns `true`, and as a result +`f` may be invoked with further elements for which the return value is also `true`. + +--- + +==== [c]visit_while + +```c++ +template bool visit_while(F f); +template bool visit_while(F f) const; +template bool cvisit_while(F f) const; +``` + +Successively invokes `f` with references to each of the elements in the table until `f` returns `false` +or all the elements are visited. +Such references to the elements are const iff `*this` is const. + +[horizontal] +Returns:;; `false` iff `f` ever returns `false`. + +--- + +==== Parallel [c]visit_while + +```c++ +template bool visit_while(ExecutionPolicy&& policy, F f); +template bool visit_while(ExecutionPolicy&& policy, F f) const; +template bool cvisit_while(ExecutionPolicy&& policy, F f) const; +``` + +Invokes `f` with references to each of the elements in the table until `f` returns `false` +or all the elements are visited. +Such references to the elements are const iff `*this` is const. +Execution is parallelized according to the semantics of the execution policy specified. + +[horizontal] +Returns:;; `false` iff `f` ever returns `false`. +Throws:;; Depending on the exception handling mechanism of the execution policy used, may call `std::terminate` if an exception is thrown within `f`. +Notes:;; Only available in compilers supporting C++17 parallel algorithms. + ++ +These overloads only participate in overload resolution if `std::is_execution_policy_v>` is `true`. + ++ +Unsequenced execution policies are not allowed. + ++ +Parallelization implies that execution does not necessary finish as soon as `f` returns `false`, and as a result +`f` may be invoked with further elements for which the return value is also `false`. + +--- + === Size and Capacity ==== empty From 470abf41d80586893ff03719b2788cb5b914c85d Mon Sep 17 00:00:00 2001 From: joaquintides Date: Mon, 24 Jul 2023 20:13:18 +0200 Subject: [PATCH 03/18] dropped [c]visit_until --- doc/unordered/changes.adoc | 2 +- doc/unordered/concurrent.adoc | 14 ++--- doc/unordered/concurrent_flat_map.adoc | 54 ------------------- .../boost/unordered/concurrent_flat_map.hpp | 50 ----------------- .../unordered/detail/foa/concurrent_table.hpp | 40 -------------- 5 files changed, 5 insertions(+), 155 deletions(-) diff --git a/doc/unordered/changes.adoc b/doc/unordered/changes.adoc index 6cf4c767..757a7000 100644 --- a/doc/unordered/changes.adoc +++ b/doc/unordered/changes.adoc @@ -8,7 +8,7 @@ == Release 1.84.0 -* Added `[c]visit_until` and `[c]visit_while` operations to `boost::concurrent_map`, +* Added `[c]visit_while` operations to `boost::concurrent_map`, with serial and parallel variants. == Release 1.83.0 - Major update diff --git a/doc/unordered/concurrent.adoc b/doc/unordered/concurrent.adoc index 1d66a53c..aed9c466 100644 --- a/doc/unordered/concurrent.adoc +++ b/doc/unordered/concurrent.adoc @@ -162,23 +162,17 @@ Traversal can be interrupted midway: int key = 0; int value = ...; -bool found = m.visit_until([&](const auto& x) { +bool found = !m.visit_while([&](const auto& x) { if(x.second == value) { key = x.first; - return true; // finish + return false; // finish } else { - return false; // keep on visiting + return true; // keep on visiting } }); if(found) { ... } - -// check if all values are != 0 - -bool all_ok = m.visit_while([&](const auto& x) { - return x.second != 0; -}); ---- There is one last whole-table visitation operation, `erase_if`: @@ -190,7 +184,7 @@ m.erase_if([](auto& x) { }); ---- -`visit_until`, `visit_while` and `erase_if` can also be parallelized. Note that, in order to increase efficiency, +`visit_while` and `erase_if` can also be parallelized. Note that, in order to increase efficiency, whole-table visitation operations do not block the table during execution: this implies that elements may be inserted, modified or erased by other threads during visitation. It is advisable not to assume too much about the exact global state of a `boost::concurrent_flat_map` diff --git a/doc/unordered/concurrent_flat_map.adoc b/doc/unordered/concurrent_flat_map.adoc index dd9199b7..8038f210 100644 --- a/doc/unordered/concurrent_flat_map.adoc +++ b/doc/unordered/concurrent_flat_map.adoc @@ -114,16 +114,6 @@ namespace boost { template void xref:#concurrent_flat_map_parallel_cvisit_all[cvisit_all](ExecutionPolicy&& policy, F f) const; - template bool xref:#concurrent_flat_map_cvisit_until[visit_until](F f); - template bool xref:#concurrent_flat_map_cvisit_until[visit_until](F f) const; - template bool xref:#concurrent_flat_map_cvisit_until[cvisit_until](F f) const; - template - bool xref:#concurrent_flat_map_parallel_cvisit_until[visit_until](ExecutionPolicy&& policy, F f); - template - bool xref:#concurrent_flat_map_parallel_cvisit_until[visit_until](ExecutionPolicy&& policy, F f) const; - template - bool xref:#concurrent_flat_map_parallel_cvisit_until[cvisit_until](ExecutionPolicy&& policy, F f) const; - template bool xref:#concurrent_flat_map_cvisit_while[visit_while](F f); template bool xref:#concurrent_flat_map_cvisit_while[visit_while](F f) const; template bool xref:#concurrent_flat_map_cvisit_while[cvisit_while](F f) const; @@ -740,50 +730,6 @@ Unsequenced execution policies are not allowed. --- -==== [c]visit_until - -```c++ -template bool visit_until(F f); -template bool visit_until(F f) const; -template bool cvisit_until(F f) const; -``` - -Successively invokes `f` with references to each of the elements in the table until `f` returns `true` -or all the elements are visited. -Such references to the elements are const iff `*this` is const. - -[horizontal] -Returns:;; `true` iff `f` ever returns `true`. - ---- - -==== Parallel [c]visit_until - -```c++ -template bool visit_until(ExecutionPolicy&& policy, F f); -template bool visit_until(ExecutionPolicy&& policy, F f) const; -template bool cvisit_until(ExecutionPolicy&& policy, F f) const; -``` - -Invokes `f` with references to each of the elements in the table until `f` returns `true` -or all the elements are visited. -Such references to the elements are const iff `*this` is const. -Execution is parallelized according to the semantics of the execution policy specified. - -[horizontal] -Returns:;; `true` iff `f` ever returns `true`. -Throws:;; Depending on the exception handling mechanism of the execution policy used, may call `std::terminate` if an exception is thrown within `f`. -Notes:;; Only available in compilers supporting C++17 parallel algorithms. + -+ -These overloads only participate in overload resolution if `std::is_execution_policy_v>` is `true`. + -+ -Unsequenced execution policies are not allowed. + -+ -Parallelization implies that execution does not necessary finish as soon as `f` returns `true`, and as a result -`f` may be invoked with further elements for which the return value is also `true`. - ---- - ==== [c]visit_while ```c++ diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 10712c9e..9364d349 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -355,56 +355,6 @@ namespace boost { } #endif - template bool visit_until(F f) - { - BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) - return table_.visit_until(f); - } - - template bool visit_until(F f) const - { - BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) - return table_.visit_until(f); - } - - template bool cvisit_until(F f) const - { - BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) - return table_.cvisit_until(f); - } - -#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) - template - typename std::enable_if::value, - bool>::type - visit_until(ExecPolicy&& p, F f) - { - BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) - BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy) - return table_.visit_until(p, f); - } - - template - typename std::enable_if::value, - bool>::type - visit_until(ExecPolicy&& p, F f) const - { - BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) - BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy) - return table_.visit_until(p, f); - } - - template - typename std::enable_if::value, - bool>::type - cvisit_until(ExecPolicy&& p, F f) const - { - BOOST_UNORDERED_STATIC_ASSERT_CONST_INVOCABLE(F) - BOOST_UNORDERED_STATIC_ASSERT_EXEC_POLICY(ExecPolicy) - return table_.cvisit_until(p, f); - } -#endif - template bool visit_while(F f) { BOOST_UNORDERED_STATIC_ASSERT_INVOCABLE(F) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 5a28f782..51615446 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -539,46 +539,6 @@ public: } #endif - template bool visit_until(F&& f) - { - return !visit_while([&](value_type& x){return !f(x);}); - } - - template bool visit_until(F&& f)const - { - return !visit_while([&](const value_type& x){return !f(x);}); - } - - template bool cvisit_until(F&& f)const - { - return visit_while(std::forward(f)); - } - -#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) - template - bool visit_until(ExecutionPolicy&& policy,F&& f) - { - return !visit_while( - std::forward(policy), - [&](value_type& x){return !f(x);}); - } - - template - bool visit_until(ExecutionPolicy&& policy,F&& f)const - { - return !visit_while( - std::forward(policy), - [&](const value_type& x){return !f(x);}); - } - - template - bool cvisit_until(ExecutionPolicy&& policy,F&& f)const - { - return visit_until( - std::forward(policy),std::forward(f)); - } -#endif - template bool visit_while(F&& f) { return visit_while_impl(group_exclusive{},std::forward(f)); From 9c476ef72a9f9d2d2df361f1f0edfa506afe9595 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Mon, 24 Jul 2023 20:19:09 +0200 Subject: [PATCH 04/18] typo --- doc/unordered/concurrent.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/unordered/concurrent.adoc b/doc/unordered/concurrent.adoc index aed9c466..7ed1be44 100644 --- a/doc/unordered/concurrent.adoc +++ b/doc/unordered/concurrent.adoc @@ -158,7 +158,7 @@ Traversal can be interrupted midway: [source,c++] ---- -// finds the key to a given (unique value) +// finds the key to a given (unique) value int key = 0; int value = ...; From a22c133c3d8347547c5c98feaf16ac53be756e1a Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Wed, 26 Jul 2023 10:14:11 -0700 Subject: [PATCH 05/18] Add tests for visit_while --- test/cfoa/visit_tests.cpp | 216 +++++++++++++++++++++++++++++++++++++- 1 file changed, 215 insertions(+), 1 deletion(-) diff --git a/test/cfoa/visit_tests.cpp b/test/cfoa/visit_tests.cpp index fff8f69d..267457df 100644 --- a/test/cfoa/visit_tests.cpp +++ b/test/cfoa/visit_tests.cpp @@ -349,6 +349,106 @@ namespace { } visit_all; + struct visit_while_type + { + template + void operator()(std::vector& values, X& x, M const& reference_map) + { + using value_type = typename X::value_type; + + auto mut_truthy_visitor = [&reference_map]( + std::atomic& num_visits) { + return [&reference_map, &num_visits](value_type& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second); + ++num_visits; + return true; + }; + }; + + auto const_truthy_visitor = [&reference_map]( + std::atomic& num_visits) { + return [&reference_map, &num_visits](value_type const& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second); + ++num_visits; + return true; + }; + }; + + auto mut_falsey_visitor = [&reference_map]( + std::atomic& num_visits) { + return [&reference_map, &num_visits](value_type& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + ++num_visits; + return (kv.second.x_ % 100) == 0; + }; + }; + + auto const_falsey_visitor = [&reference_map]( + std::atomic& num_visits) { + return [&reference_map, &num_visits](value_type const& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + ++num_visits; + return (kv.second.x_ % 100) == 0; + }; + }; + + { + thread_runner(values, [&x, &mut_truthy_visitor](boost::span) { + std::atomic num_visits{0}; + BOOST_TEST(x.visit_while(mut_truthy_visitor(num_visits))); + BOOST_TEST_EQ(x.size(), num_visits); + }); + } + + { + thread_runner(values, [&x, &const_truthy_visitor](boost::span) { + std::atomic num_visits{0}; + auto const& y = x; + BOOST_TEST(y.visit_while(const_truthy_visitor(num_visits))); + BOOST_TEST_EQ(x.size(), num_visits); + }); + } + + { + thread_runner(values, [&x, &const_truthy_visitor](boost::span) { + std::atomic num_visits{0}; + BOOST_TEST(x.cvisit_while(const_truthy_visitor(num_visits))); + BOOST_TEST_EQ(x.size(), num_visits); + }); + } + + { + thread_runner(values, [&x, &mut_falsey_visitor](boost::span) { + std::atomic num_visits{0}; + BOOST_TEST_NOT(x.visit_while(mut_falsey_visitor(num_visits))); + BOOST_TEST_LT(num_visits, x.size()); + BOOST_TEST_GT(num_visits, 0u); + }); + } + + { + thread_runner(values, [&x, &const_falsey_visitor](boost::span) { + std::atomic num_visits{0}; + auto const& y = x; + BOOST_TEST_NOT(y.visit_while(const_falsey_visitor(num_visits))); + BOOST_TEST_LT(num_visits, x.size()); + BOOST_TEST_GT(num_visits, 0u); + }); + } + + { + thread_runner(values, [&x, &const_falsey_visitor](boost::span) { + std::atomic num_visits{0}; + BOOST_TEST_NOT(x.cvisit_while(const_falsey_visitor(num_visits))); + BOOST_TEST_LT(num_visits, x.size()); + BOOST_TEST_GT(num_visits, 0u); + }); + } + } + } visit_while; + struct exec_policy_visit_all_type { template @@ -407,6 +507,120 @@ namespace { } } exec_policy_visit_all; + struct exec_policy_visit_while_type + { + template + void operator()(std::vector& values, X& x, M const& reference_map) + { +#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS) + using value_type = typename X::value_type; + + auto mut_truthy_visitor = [&reference_map]( + std::atomic& num_visits) { + return [&reference_map, &num_visits](value_type& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second); + ++num_visits; + return true; + }; + }; + + auto const_truthy_visitor = [&reference_map]( + std::atomic& num_visits) { + return [&reference_map, &num_visits](value_type const& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second); + ++num_visits; + return true; + }; + }; + + auto mut_falsey_visitor = [&reference_map]( + std::atomic& num_visits) { + return [&reference_map, &num_visits](value_type& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second); + ++num_visits; + return (kv.second.x_ % 100) == 0; + }; + }; + + auto const_falsey_visitor = [&reference_map]( + std::atomic& num_visits) { + return [&reference_map, &num_visits](value_type const& kv) { + BOOST_TEST(reference_map.contains(kv.first)); + BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second); + ++num_visits; + return (kv.second.x_ % 100) == 0; + }; + }; + + { + thread_runner(values, [&x, &mut_truthy_visitor](boost::span) { + std::atomic num_visits{0}; + BOOST_TEST( + x.visit_while(std::execution::par, mut_truthy_visitor(num_visits))); + BOOST_TEST_EQ(x.size(), num_visits); + }); + } + + { + thread_runner(values, [&x, &const_truthy_visitor](boost::span) { + std::atomic num_visits{0}; + auto const& y = x; + BOOST_TEST(y.visit_while( + std::execution::par, const_truthy_visitor(num_visits))); + BOOST_TEST_EQ(x.size(), num_visits); + }); + } + + { + thread_runner(values, [&x, &const_truthy_visitor](boost::span) { + std::atomic num_visits{0}; + BOOST_TEST(x.cvisit_while( + std::execution::par, const_truthy_visitor(num_visits))); + BOOST_TEST_EQ(x.size(), num_visits); + }); + } + + { + thread_runner(values, [&x, &mut_falsey_visitor](boost::span) { + std::atomic num_visits{0}; + BOOST_TEST_NOT( + x.visit_while(std::execution::par, mut_falsey_visitor(num_visits))); + BOOST_TEST_LT(num_visits, x.size()); + BOOST_TEST_GT(num_visits, 0u); + }); + } + + { + thread_runner(values, [&x, &const_falsey_visitor](boost::span) { + std::atomic num_visits{0}; + auto const& y = x; + BOOST_TEST_NOT(y.visit_while( + std::execution::par, const_falsey_visitor(num_visits))); + BOOST_TEST_LT(num_visits, x.size()); + BOOST_TEST_GT(num_visits, 0u); + }); + } + + { + thread_runner(values, [&x, &const_falsey_visitor](boost::span) { + std::atomic num_visits{0}; + BOOST_TEST_NOT(x.cvisit_while( + std::execution::par, const_falsey_visitor(num_visits))); + BOOST_TEST_LT(num_visits, x.size()); + BOOST_TEST_GT(num_visits, 0u); + }); + } +#else + (void)values; + (void)x; + (void)reference_map; +#endif + } + } exec_policy_visit_while; + template void visit(X*, G gen, F visitor, test::random_generator rg) { @@ -570,7 +784,7 @@ UNORDERED_TEST( visit, ((map)) ((value_type_generator)(init_type_generator)) - ((lvalue_visitor)(visit_all)(exec_policy_visit_all)) + ((lvalue_visitor)(visit_all)(visit_while)(exec_policy_visit_all)(exec_policy_visit_while)) ((default_generator)(sequential)(limited_range))) UNORDERED_TEST( From 5339bf67d9a445e8f4acb8a3fd963c128e259b80 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Thu, 3 Aug 2023 10:52:03 -0700 Subject: [PATCH 06/18] Add missing #include's --- include/boost/unordered/detail/foa/node_map_types.hpp | 1 + include/boost/unordered/detail/foa/node_set_types.hpp | 1 + 2 files changed, 2 insertions(+) diff --git a/include/boost/unordered/detail/foa/node_map_types.hpp b/include/boost/unordered/detail/foa/node_map_types.hpp index 0853dfe9..8b4a4b91 100644 --- a/include/boost/unordered/detail/foa/node_map_types.hpp +++ b/include/boost/unordered/detail/foa/node_map_types.hpp @@ -6,6 +6,7 @@ #define BOOST_UNORDERED_DETAIL_FOA_NODE_MAP_TYPES_HPP #include +#include #include namespace boost { diff --git a/include/boost/unordered/detail/foa/node_set_types.hpp b/include/boost/unordered/detail/foa/node_set_types.hpp index 68c20986..f8aaa8e0 100644 --- a/include/boost/unordered/detail/foa/node_set_types.hpp +++ b/include/boost/unordered/detail/foa/node_set_types.hpp @@ -6,6 +6,7 @@ #define BOOST_UNORDERED_DETAIL_FOA_NODE_SET_TYPES_HPP #include +#include #include namespace boost { From 6bf84067b3292cd7cfcfba90e90bb3366e926df4 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 22 Jul 2023 17:59:01 +0200 Subject: [PATCH 07/18] added unordered_flat_map(concurrent_flat_map&&) --- .../boost/unordered/concurrent_flat_map.hpp | 4 +++ .../unordered/detail/foa/concurrent_table.hpp | 18 +++++++--- include/boost/unordered/detail/foa/core.hpp | 36 +++++++++++++------ include/boost/unordered/detail/foa/table.hpp | 28 +++++++++++++++ .../boost/unordered/unordered_flat_map.hpp | 7 ++++ 5 files changed, 78 insertions(+), 15 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index 9364d349..a5b968c8 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -84,6 +85,9 @@ namespace boost { template friend class concurrent_flat_map; + template + friend class unordered_flat_map; using type_policy = detail::foa::flat_map_types; diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 51615446..84a66c4e 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -281,6 +281,13 @@ struct concurrent_table_arrays:table_arrays template static void delete_(Allocator& al,concurrent_table_arrays& arrays)noexcept + { + delete_group_access(al,arrays); + super::delete_(al,arrays); + } + + template + static void delete_group_access(Allocator& al,concurrent_table_arrays& arrays)noexcept { if(arrays.elements){ using access_alloc= @@ -294,7 +301,6 @@ struct concurrent_table_arrays:table_arrays aal,pointer_traits::pointer_to(*arrays.group_accesses), arrays.groups_size_mask+1); } - super::delete_(al,arrays); } group_access *group_accesses; @@ -391,6 +397,9 @@ inline void swap(atomic_size_control& x,atomic_size_control& y) * over. */ +template +class table; /* concurrent/non-concurrent interop */ + template using concurrent_table_core_impl=table_core< TypePolicy,group15,concurrent_table_arrays, @@ -413,10 +422,6 @@ class concurrent_table: using super::N; using prober=typename super::prober; - template< - typename TypePolicy2,typename Hash2,typename Pred2,typename Allocator2> - friend class concurrent_table; - public: using key_type=typename super::key_type; using init_type=typename super::init_type; @@ -875,6 +880,9 @@ public: } private: + template friend class concurrent_table; + template friend class table; + using mutex_type=rw_spinlock; using multimutex_type=multimutex; // TODO: adapt 128 to the machine using shared_lock_guard=shared_lock; diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 239d05d3..ae5c29c7 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1282,6 +1282,17 @@ public: size_ctrl{initial_max_load(),0} {} + /* bare transfer ctor for concurrent/non-concurrent interop */ + + table_core( + Hash&& h_,Pred&& pred_,Allocator&& al_, + const arrays_type& arrays_,const size_ctrl_type& size_ctrl_): + hash_base{empty_init,h_}, + pred_base{empty_init,pred_}, + allocator_base{empty_init,al_}, + arrays(arrays_),size_ctrl(size_ctrl_) + {} + table_core(const table_core& x): table_core{x,alloc_traits::select_on_container_copy_construction(x.al())}{} @@ -1290,14 +1301,11 @@ public: std::is_nothrow_move_constructible::value&& std::is_nothrow_move_constructible::value&& std::is_nothrow_move_constructible::value): - hash_base{empty_init,std::move(x.h())}, - pred_base{empty_init,std::move(x.pred())}, - allocator_base{empty_init,std::move(x.al())}, - arrays(x.arrays),size_ctrl(x.size_ctrl) + table_core{ + std::move(x.h()),std::move(x.pred()),std::move(x.al()), + x.arrays,x.size_ctrl} { - x.arrays=x.new_arrays(0); - x.size_ctrl.ml=x.initial_max_load(); - x.size_ctrl.size=0; + x.empty_initialize(); } table_core(const table_core& x,const Allocator& al_): @@ -1336,6 +1344,13 @@ public: delete_arrays(arrays); } + void empty_initialize()noexcept + { + arrays=new_arrays(0); + size_ctrl.ml=initial_max_load(); + size_ctrl.size=0; + } + table_core& operator=(const table_core& x) { BOOST_UNORDERED_STATIC_ASSERT_HASH_PRED(Hash, Pred) @@ -1802,9 +1817,10 @@ private: table_core(Hash&& h_,Pred&& pred_,const Allocator& al_): hash_base{empty_init,std::move(h_)}, pred_base{empty_init,std::move(pred_)}, - allocator_base{empty_init,al_},arrays(new_arrays(0)), - size_ctrl{initial_max_load(),0} - {} + allocator_base{empty_init,al_} + { + empty_initialize(); + } arrays_type new_arrays(std::size_t n) { diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index 85145cb6..3c4ab5ac 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -264,6 +264,9 @@ private: * checking is done by boost::unordered_(flat|node)_(map|set). */ +template +class concurrent_table; /* concurrent/non-concurrent interop */ + template using table_core_impl= table_core,table_arrays, @@ -284,6 +287,8 @@ class table:table_core_impl using group_type=typename super::group_type; using super::N; using prober=typename super::prober; + using arrays_type=typename super::arrays_type; + using size_ctrl_type=typename super::size_ctrl_type; using locator=typename super::locator; public: @@ -323,6 +328,8 @@ public: table(table&& x)=default; table(const table& x,const Allocator& al_):super{x,al_}{} table(table&& x,const Allocator& al_):super{std::move(x),al_}{} + table(concurrent_table&& x): + table(std::move(x),x.exclusive_access()){} ~table()=default; table& operator=(const table& x)=default; @@ -496,6 +503,27 @@ public: friend bool operator!=(const table& x,const table& y){return !(x==y);} private: + template friend class concurrent_table; + using compatible_concurrent_table= + concurrent_table; + + table( + compatible_concurrent_table&& x, + typename compatible_concurrent_table::exclusive_lock_guard): + super{ + std::move(x.h()),std::move(x.pred()),std::move(x.al()), + arrays_type{ + x.arrays.groups_size_index,x.arrays.groups_size_mask, + reinterpret_cast(x.arrays.groups), + reinterpret_cast(x.arrays.elements)}, + size_ctrl_type{ + x.size_ctrl.ml,x.size_ctrl.size}} + { + typename compatible_concurrent_table::arrays_type::delete_group_access( + this->al(),x.arrays); + x.empty_initialize(); + } + struct erase_on_exit { erase_on_exit(table& x_,const_iterator it_):x{x_},it{it_}{} diff --git a/include/boost/unordered/unordered_flat_map.hpp b/include/boost/unordered/unordered_flat_map.hpp index d74de55a..66ce6faf 100644 --- a/include/boost/unordered/unordered_flat_map.hpp +++ b/include/boost/unordered/unordered_flat_map.hpp @@ -10,6 +10,7 @@ #pragma once #endif +#include #include #include #include @@ -173,6 +174,12 @@ namespace boost { { } + unordered_flat_map( + concurrent_flat_map&& other) + : table_(std::move(other.table_)) + { + } + ~unordered_flat_map() = default; unordered_flat_map& operator=(unordered_flat_map const& other) From 6994a37b235c78324b3b0c542bad683fb61be547 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 22 Jul 2023 18:49:15 +0200 Subject: [PATCH 08/18] used direct arrays construction in place of empty_initialize (arrays_type not default constructible) --- include/boost/unordered/detail/foa/core.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index ae5c29c7..78ff2de6 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1817,9 +1817,9 @@ private: table_core(Hash&& h_,Pred&& pred_,const Allocator& al_): hash_base{empty_init,std::move(h_)}, pred_base{empty_init,std::move(pred_)}, - allocator_base{empty_init,al_} + allocator_base{empty_init,al_},arrays(new_arrays(0)), + size_ctrl{initial_max_load(),0} { - empty_initialize(); } arrays_type new_arrays(std::size_t n) From 4be37cfdaf57f24515ff1108848972e910902b57 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 22 Jul 2023 19:17:11 +0200 Subject: [PATCH 09/18] avoided premature instantiation of concurrent_table --- include/boost/unordered/detail/foa/table.hpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index 3c4ab5ac..2a8a0b5e 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -507,9 +507,8 @@ private: using compatible_concurrent_table= concurrent_table; - table( - compatible_concurrent_table&& x, - typename compatible_concurrent_table::exclusive_lock_guard): + template + table(compatible_concurrent_table&& x,ExclusiveLockGuard): super{ std::move(x.h()),std::move(x.pred()),std::move(x.al()), arrays_type{ From c046b916f8bf34a8f779777daaee9e04125c913a Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 22 Jul 2023 20:38:16 +0200 Subject: [PATCH 10/18] fix atomic_size_control copy ctor --- include/boost/unordered/detail/foa/concurrent_table.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 84a66c4e..afe9cdae 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -313,7 +313,7 @@ struct atomic_size_control atomic_size_control(std::size_t ml_,std::size_t size_): pad0_{},ml{ml_},pad1_{},size{size_}{} - atomic_size_control(atomic_size_control& x): + atomic_size_control(const atomic_size_control& x): pad0_{},ml{x.ml.load()},pad1_{},size{x.size.load()}{} /* padding to avoid false sharing internally and with sorrounding data */ From d4adcd9b712db94262fbd0fb84cd79148337c4cf Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sat, 22 Jul 2023 20:48:06 +0200 Subject: [PATCH 11/18] added missing std::move's --- include/boost/unordered/detail/foa/core.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/boost/unordered/detail/foa/core.hpp b/include/boost/unordered/detail/foa/core.hpp index 78ff2de6..f0a2ef5c 100644 --- a/include/boost/unordered/detail/foa/core.hpp +++ b/include/boost/unordered/detail/foa/core.hpp @@ -1287,9 +1287,9 @@ public: table_core( Hash&& h_,Pred&& pred_,Allocator&& al_, const arrays_type& arrays_,const size_ctrl_type& size_ctrl_): - hash_base{empty_init,h_}, - pred_base{empty_init,pred_}, - allocator_base{empty_init,al_}, + hash_base{empty_init,std::move(h_)}, + pred_base{empty_init,std::move(pred_)}, + allocator_base{empty_init,std::move(al_)}, arrays(arrays_),size_ctrl(size_ctrl_) {} From ac1a236de661ab5b8d3138b1e6c7f215cd85c6b7 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sun, 23 Jul 2023 10:15:06 +0200 Subject: [PATCH 12/18] added concurrent_flat_map(unordered_flat_map&&) --- .../boost/unordered/concurrent_flat_map.hpp | 7 +++ .../unordered/detail/foa/concurrent_table.hpp | 50 ++++++++++++++----- .../boost/unordered/unordered_flat_map.hpp | 4 ++ 3 files changed, 48 insertions(+), 13 deletions(-) diff --git a/include/boost/unordered/concurrent_flat_map.hpp b/include/boost/unordered/concurrent_flat_map.hpp index a5b968c8..6e2eef98 100644 --- a/include/boost/unordered/concurrent_flat_map.hpp +++ b/include/boost/unordered/concurrent_flat_map.hpp @@ -227,6 +227,13 @@ namespace boost { { } + + concurrent_flat_map( + unordered_flat_map&& other) + : table_(std::move(other.table_)) + { + } + ~concurrent_flat_map() = default; concurrent_flat_map& operator=(concurrent_flat_map const& rhs) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index afe9cdae..5fcb6ace 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -252,7 +252,21 @@ struct concurrent_table_arrays:table_arrays template static concurrent_table_arrays new_(Allocator& al,std::size_t n) { - concurrent_table_arrays arrays{super::new_(al,n),nullptr}; + super x{super::new_(al,n)}; + BOOST_TRY{ + return new_group_access(al,x); + } + BOOST_CATCH(...){ + super::delete_(al,x); + BOOST_RETHROW + } + BOOST_CATCH_END + } + + template + static concurrent_table_arrays new_group_access(Allocator& al,const super& x) + { + concurrent_table_arrays arrays{x,nullptr}; if(!arrays.elements){ arrays.group_accesses=dummy_group_accesses(); } @@ -261,20 +275,13 @@ struct concurrent_table_arrays:table_arrays typename boost::allocator_rebind::type; using access_traits=boost::allocator_traits; - BOOST_TRY{ - auto aal=access_alloc(al); - arrays.group_accesses=boost::to_address( - access_traits::allocate(aal,arrays.groups_size_mask+1)); + auto aal=access_alloc(al); + arrays.group_accesses=boost::to_address( + access_traits::allocate(aal,arrays.groups_size_mask+1)); - for(std::size_t i=0;i&& x): + super{ + std::move(x.h()),std::move(x.pred()),std::move(x.al()), + arrays_type(arrays_type::new_group_access( + x.al(), + typename arrays_type::super{ + x.arrays.groups_size_index,x.arrays.groups_size_mask, + reinterpret_cast(x.arrays.groups), + reinterpret_cast(x.arrays.elements)})), + size_ctrl_type{x.size_ctrl.ml,x.size_ctrl.size}} + { + x.empty_initialize(); + } + ~concurrent_table()=default; concurrent_table& operator=(const concurrent_table& x) diff --git a/include/boost/unordered/unordered_flat_map.hpp b/include/boost/unordered/unordered_flat_map.hpp index 66ce6faf..1f7984ec 100644 --- a/include/boost/unordered/unordered_flat_map.hpp +++ b/include/boost/unordered/unordered_flat_map.hpp @@ -37,6 +37,10 @@ namespace boost { template class unordered_flat_map { + template + friend class concurrent_flat_map; + using map_types = detail::foa::flat_map_types; using table_type = detail::foa::table Date: Sun, 23 Jul 2023 10:49:53 +0200 Subject: [PATCH 13/18] stylistic --- include/boost/unordered/detail/foa/concurrent_table.hpp | 7 ++++--- include/boost/unordered/detail/foa/table.hpp | 9 ++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 5fcb6ace..15aec185 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -372,7 +372,7 @@ inline void swap(atomic_size_control& x,atomic_size_control& y) * - Parallel versions of [c]visit_all(f) and erase_if(f) are provided based * on C++17 stdlib parallel algorithms. * - * Consult boost::unordered_flat_map docs for the full API reference. + * Consult boost::concurrent_flat_map docs for the full API reference. * Heterogeneous lookup is suported by default, that is, without checking for * any ::is_transparent typedefs --this checking is done by the wrapping * containers. @@ -430,6 +430,8 @@ class concurrent_table: using prober=typename super::prober; using arrays_type=typename super::arrays_type; using size_ctrl_type=typename super::size_ctrl_type; + using compatible_nonconcurrent_table=table; + friend class compatible_nonconcurrent_table; public: using key_type=typename super::key_type; @@ -465,7 +467,7 @@ public: concurrent_table(concurrent_table&& x,const Allocator& al_): concurrent_table(std::move(x),al_,x.exclusive_access()){} - concurrent_table(table&& x): + concurrent_table(compatible_nonconcurrent_table&& x): super{ std::move(x.h()),std::move(x.pred()),std::move(x.al()), arrays_type(arrays_type::new_group_access( @@ -905,7 +907,6 @@ public: private: template friend class concurrent_table; - template friend class table; using mutex_type=rw_spinlock; using multimutex_type=multimutex; // TODO: adapt 128 to the machine diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index 2a8a0b5e..d8968623 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -290,6 +290,9 @@ class table:table_core_impl using arrays_type=typename super::arrays_type; using size_ctrl_type=typename super::size_ctrl_type; using locator=typename super::locator; + using compatible_concurrent_table= + concurrent_table; + friend class compatible_concurrent_table; public: using key_type=typename super::key_type; @@ -328,7 +331,7 @@ public: table(table&& x)=default; table(const table& x,const Allocator& al_):super{x,al_}{} table(table&& x,const Allocator& al_):super{std::move(x),al_}{} - table(concurrent_table&& x): + table(compatible_concurrent_table&& x): table(std::move(x),x.exclusive_access()){} ~table()=default; @@ -503,10 +506,6 @@ public: friend bool operator!=(const table& x,const table& y){return !(x==y);} private: - template friend class concurrent_table; - using compatible_concurrent_table= - concurrent_table; - template table(compatible_concurrent_table&& x,ExclusiveLockGuard): super{ From bf4a5efd2d8cab5f593f58bc9fde2872ce9b332e Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sun, 23 Jul 2023 12:23:08 +0200 Subject: [PATCH 14/18] documented concurrent/unordered interop --- doc/unordered/changes.adoc | 2 ++ doc/unordered/concurrent.adoc | 26 ++++++++++++++++++++++++++ doc/unordered/concurrent_flat_map.adoc | 16 ++++++++++++++++ doc/unordered/unordered_flat_map.adoc | 17 +++++++++++++++++ 4 files changed, 61 insertions(+) diff --git a/doc/unordered/changes.adoc b/doc/unordered/changes.adoc index 757a7000..1d760650 100644 --- a/doc/unordered/changes.adoc +++ b/doc/unordered/changes.adoc @@ -10,6 +10,8 @@ * Added `[c]visit_while` operations to `boost::concurrent_map`, with serial and parallel variants. +* Added efficient move construction of `boost::unordered_flat_map` from +`boost::concurrent_flat_map` and vice versa. == Release 1.83.0 - Major update diff --git a/doc/unordered/concurrent.adoc b/doc/unordered/concurrent.adoc index 7ed1be44..d418cec4 100644 --- a/doc/unordered/concurrent.adoc +++ b/doc/unordered/concurrent.adoc @@ -201,3 +201,29 @@ and the user need not take any special precaution, but overall performance may b Another blocking operation is _rehashing_, which happens explicitly via `rehash`/`reserve` or during insertion when the table's load hits `max_load()`. As with non-concurrent containers, reserving space in advance of bulk insertions will generally speed up the process. + +== Interoperability with non-concurrent containers + +As their internal data structure is basically the same, `boost::unordered_flat_map` can +be efficiently move-constructed from `boost::concurrent_flat_map` and vice versa. +This interoperability comes handy in multistage scenarios where parts of the data processing happen +in parallel whereas other steps are non-concurrent (or non-modifying). In the following example, +we want to construct a histogram from a huge input vector of words: +the population phase can be done in parallel with `boost::concurrent_flat_map` and results +then transferred to the final container. + +[source,c++] +---- +std::vector words = ...; + +// Insert words in parallel +boost::concurrent_flat_map m0; +std::for_each( + std::execution::par, words.begin(), words.end(), + [&](const auto& word) { + m0.try_emplace_or_visit(word, 1, [](auto& x) { ++x.second; }); + }); + +// Transfer to a regular unordered_flat_map +boost::unordered_flat_map m=std::move(m0); +---- diff --git a/doc/unordered/concurrent_flat_map.adoc b/doc/unordered/concurrent_flat_map.adoc index 8038f210..bca4f5bb 100644 --- a/doc/unordered/concurrent_flat_map.adoc +++ b/doc/unordered/concurrent_flat_map.adoc @@ -69,6 +69,7 @@ namespace boost { explicit xref:#concurrent_flat_map_allocator_constructor[concurrent_flat_map](const Allocator& a); xref:#concurrent_flat_map_copy_constructor_with_allocator[concurrent_flat_map](const concurrent_flat_map& other, const Allocator& a); xref:#concurrent_flat_map_move_constructor_with_allocator[concurrent_flat_map](concurrent_flat_map&& other, const Allocator& a); + xref:#concurrent_flat_map_move_constructor_from_unordered_flat_map[concurrent_flat_map](unordered_flat_map&& other); xref:#concurrent_flat_map_initializer_list_constructor[concurrent_flat_map](std::initializer_list il, size_type n = _implementation-defined_ const hasher& hf = hasher(), @@ -501,6 +502,21 @@ Concurrency:;; Blocking on `other`. --- +==== Move Constructor from unordered_flat_map + +```c++ +concurrent_flat_map(unordered_flat_map&& other); +``` + +Move construction from a xref:#unordered_flat_map[`unordered_flat_map`]. +The internal bucket array of `other` is transferred directly to the new container. +The hash function, predicate and allocator are moved-constructed from `other`. + +[horizontal] +Complexity:;; O(`bucket_count()`) + +--- + ==== Initializer List Constructor [source,c++,subs="+quotes"] ---- diff --git a/doc/unordered/unordered_flat_map.adoc b/doc/unordered/unordered_flat_map.adoc index a112e192..543db307 100644 --- a/doc/unordered/unordered_flat_map.adoc +++ b/doc/unordered/unordered_flat_map.adoc @@ -77,6 +77,7 @@ namespace boost { explicit xref:#unordered_flat_map_allocator_constructor[unordered_flat_map](const Allocator& a); xref:#unordered_flat_map_copy_constructor_with_allocator[unordered_flat_map](const unordered_flat_map& other, const Allocator& a); xref:#unordered_flat_map_move_constructor_with_allocator[unordered_flat_map](unordered_flat_map&& other, const Allocator& a); + xref:#unordered_flat_map_move_constructor_from_concurrent_flat_map[unordered_flat_map](concurrent_flat_map&& other); xref:#unordered_flat_map_initializer_list_constructor[unordered_flat_map](std::initializer_list il, size_type n = _implementation-defined_ const hasher& hf = hasher(), @@ -472,6 +473,22 @@ from `other`, and the allocator is copy-constructed from `a`. --- +==== Move Constructor from concurrent_flat_map + +```c++ +unordered_flat_map(concurrent_flat_map&& other); +``` + +Move construction from a xref:#concurrent_flat_map[`concurrent_flat_map`]. +The internal bucket array of `other` is transferred directly to the new container. +The hash function, predicate and allocator are moved-constructed from `other`. + +[horizontal] +Complexity:;; Constant time. +Concurrency:;; Blocking on `other`. + +--- + ==== Initializer List Constructor [source,c++,subs="+quotes"] ---- From b206513a11b61dd7cb4d391320c698a227f18337 Mon Sep 17 00:00:00 2001 From: joaquintides Date: Sun, 23 Jul 2023 14:39:23 +0200 Subject: [PATCH 15/18] fixed friend declarations --- include/boost/unordered/detail/foa/concurrent_table.hpp | 2 +- include/boost/unordered/detail/foa/table.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/boost/unordered/detail/foa/concurrent_table.hpp b/include/boost/unordered/detail/foa/concurrent_table.hpp index 15aec185..4a536de0 100644 --- a/include/boost/unordered/detail/foa/concurrent_table.hpp +++ b/include/boost/unordered/detail/foa/concurrent_table.hpp @@ -431,7 +431,7 @@ class concurrent_table: using arrays_type=typename super::arrays_type; using size_ctrl_type=typename super::size_ctrl_type; using compatible_nonconcurrent_table=table; - friend class compatible_nonconcurrent_table; + friend compatible_nonconcurrent_table; public: using key_type=typename super::key_type; diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index d8968623..7c462d01 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -292,7 +292,7 @@ class table:table_core_impl using locator=typename super::locator; using compatible_concurrent_table= concurrent_table; - friend class compatible_concurrent_table; + friend compatible_concurrent_table; public: using key_type=typename super::key_type; From ce076782fdacaac637e3c57b7a48c32894a664d0 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Mon, 24 Jul 2023 13:07:34 -0700 Subject: [PATCH 16/18] Add tests for interop constructors --- test/cfoa/constructor_tests.cpp | 108 ++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/test/cfoa/constructor_tests.cpp b/test/cfoa/constructor_tests.cpp index f6e0e069..595cc281 100644 --- a/test/cfoa/constructor_tests.cpp +++ b/test/cfoa/constructor_tests.cpp @@ -775,6 +775,109 @@ namespace { check_raii_counts(); } + template void flat_map_constructor(G gen, 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(), values.size()); + + raii::reset_counts(); + + { + boost::unordered_flat_map + flat_map(values.begin(), values.end(), reference_map.size(), hasher(1), + key_equal(2), allocator_type(3)); + + auto const old_dc = +raii::default_constructor; + auto const old_mc = +raii::move_constructor; + 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); + + map_type x(std::move(flat_map)); + + test_fuzzy_matches_reference(x, reference_map, rg); + + BOOST_TEST_EQ(+raii::default_constructor, old_dc); + BOOST_TEST_EQ(+raii::move_constructor, old_mc); + BOOST_TEST_EQ(+raii::copy_constructor, old_cc); + + BOOST_TEST_EQ(x.hash_function(), hasher(1)); + BOOST_TEST_EQ(x.key_eq(), key_equal(2)); + BOOST_TEST(x.get_allocator() == allocator_type(3)); + + BOOST_TEST(flat_map.empty()); + } + + check_raii_counts(); + + { + boost::unordered_flat_map + flat_map(0, hasher(1), key_equal(2), allocator_type(3)); + + map_type x(std::move(flat_map)); + + BOOST_TEST(x.empty()); + + BOOST_TEST_EQ(x.hash_function(), hasher(1)); + BOOST_TEST_EQ(x.key_eq(), key_equal(2)); + BOOST_TEST(x.get_allocator() == allocator_type(3)); + + BOOST_TEST(flat_map.empty()); + } + + check_raii_counts(); + + { + map_type flat_map(values.begin(), values.end(), reference_map.size(), + hasher(1), key_equal(2), allocator_type(3)); + + auto const old_dc = +raii::default_constructor; + auto const old_mc = +raii::move_constructor; + 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::unordered_flat_map + x(std::move(flat_map)); + + BOOST_TEST(x == reference_map); + + BOOST_TEST_EQ(+raii::default_constructor, old_dc); + BOOST_TEST_EQ(+raii::move_constructor, old_mc); + BOOST_TEST_EQ(+raii::copy_constructor, old_cc); + + BOOST_TEST_EQ(x.hash_function(), hasher(1)); + BOOST_TEST_EQ(x.key_eq(), key_equal(2)); + BOOST_TEST(x.get_allocator() == allocator_type(3)); + + BOOST_TEST(flat_map.empty()); + } + + check_raii_counts(); + + { + map_type flat_map(0, hasher(1), key_equal(2), allocator_type(3)); + + boost::unordered_flat_map + x(std::move(flat_map)); + + BOOST_TEST(x.empty()); + + BOOST_TEST_EQ(x.hash_function(), hasher(1)); + BOOST_TEST_EQ(x.key_eq(), key_equal(2)); + BOOST_TEST(x.get_allocator() == allocator_type(3)); + + BOOST_TEST(flat_map.empty()); + } + + check_raii_counts(); + } } // namespace // clang-format off @@ -818,6 +921,11 @@ UNORDERED_TEST( ((value_type_generator)) ((default_generator)(sequential)(limited_range))) +UNORDERED_TEST( + flat_map_constructor, + ((value_type_generator)) + ((default_generator)(sequential)(limited_range))) + // clang-format on RUN_TESTS() From e563c89b1c318a5ed76644a08b7591bf15f9f99f Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Mon, 24 Jul 2023 13:07:59 -0700 Subject: [PATCH 17/18] Remove extraneous typename --- include/boost/unordered/detail/foa/table.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/unordered/detail/foa/table.hpp b/include/boost/unordered/detail/foa/table.hpp index 7c462d01..6cc11ed8 100644 --- a/include/boost/unordered/detail/foa/table.hpp +++ b/include/boost/unordered/detail/foa/table.hpp @@ -517,7 +517,7 @@ private: size_ctrl_type{ x.size_ctrl.ml,x.size_ctrl.size}} { - typename compatible_concurrent_table::arrays_type::delete_group_access( + compatible_concurrent_table::arrays_type::delete_group_access( this->al(),x.arrays); x.empty_initialize(); } From 4918bb6b7eb3f1f1fcacd92aea3ad5bf761086c6 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Tue, 25 Jul 2023 12:05:08 -0700 Subject: [PATCH 18/18] Add tests for interop assignment --- test/cfoa/assign_tests.cpp | 138 +++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/test/cfoa/assign_tests.cpp b/test/cfoa/assign_tests.cpp index 24675d87..f2dd2364 100644 --- a/test/cfoa/assign_tests.cpp +++ b/test/cfoa/assign_tests.cpp @@ -33,6 +33,9 @@ using hasher = stateful_hash; using key_equal = stateful_key_equal; using allocator_type = stateful_allocator >; +using flat_map_type = boost::unordered::unordered_flat_map; + using map_type = boost::unordered::concurrent_flat_map; @@ -843,6 +846,136 @@ namespace { } check_raii_counts(); } + + template void flat_map_move_assign(G gen, 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()); + + /* + * basically test that a temporary container is materialized and we + * move-assign from that + * + * we don't need to be super rigorous here because we already have tests for + * container assignment, we're just testing that a temporary is materialized + */ + + { + raii::reset_counts(); + + flat_map_type flat_map(values.begin(), values.end(), values.size(), + hasher(1), key_equal(2), allocator_type(3)); + + map_type map(0, hasher(2), key_equal(1), allocator_type(3)); + + BOOST_TEST(flat_map.get_allocator() == map.get_allocator()); + + map = std::move(flat_map); + + BOOST_TEST(flat_map.empty()); + BOOST_TEST_EQ(map.size(), reference_map.size()); + + test_fuzzy_matches_reference(map, reference_map, rg); + + BOOST_TEST_EQ(map.hash_function(), hasher(1)); + BOOST_TEST_EQ(map.key_eq(), key_equal(2)); + + BOOST_TEST_EQ(raii::copy_constructor, 2 * values.size()); + BOOST_TEST_EQ(raii::destructor, 2 * values.size()); + BOOST_TEST_EQ(raii::move_constructor, 2 * reference_map.size()); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + + check_raii_counts(); + + { + raii::reset_counts(); + + map_type map(values.begin(), values.end(), values.size(), hasher(1), + key_equal(2), allocator_type(3)); + + flat_map_type flat_map(0, hasher(2), key_equal(1), allocator_type(3)); + + BOOST_TEST(flat_map.get_allocator() == map.get_allocator()); + + flat_map = std::move(map); + + BOOST_TEST(map.empty()); + BOOST_TEST_EQ(flat_map.size(), reference_map.size()); + + BOOST_TEST_EQ(flat_map.hash_function(), hasher(1)); + BOOST_TEST_EQ(flat_map.key_eq(), key_equal(2)); + + BOOST_TEST_EQ(raii::copy_constructor, 2 * values.size()); + BOOST_TEST_EQ(raii::destructor, 2 * values.size()); + BOOST_TEST_EQ(raii::move_constructor, 2 * reference_map.size()); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + + check_raii_counts(); + + { + raii::reset_counts(); + + flat_map_type flat_map(values.begin(), values.end(), values.size(), + hasher(1), key_equal(2), allocator_type(3)); + + map_type map(0, hasher(2), key_equal(1), allocator_type(4)); + + BOOST_TEST(flat_map.get_allocator() != map.get_allocator()); + + map = std::move(flat_map); + + BOOST_TEST(flat_map.empty()); + BOOST_TEST_EQ(map.size(), reference_map.size()); + + test_fuzzy_matches_reference(map, reference_map, rg); + + BOOST_TEST_EQ(map.hash_function(), hasher(1)); + BOOST_TEST_EQ(map.key_eq(), key_equal(2)); + + BOOST_TEST_EQ(raii::copy_constructor, 2 * values.size()); + BOOST_TEST_EQ( + raii::destructor, 2 * values.size() + 2 * reference_map.size()); + BOOST_TEST_EQ(raii::move_constructor, 4 * reference_map.size()); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + + check_raii_counts(); + + { + raii::reset_counts(); + + map_type map(values.begin(), values.end(), values.size(), hasher(1), + key_equal(2), allocator_type(3)); + + flat_map_type flat_map(0, hasher(2), key_equal(1), allocator_type(4)); + + BOOST_TEST(flat_map.get_allocator() != map.get_allocator()); + + flat_map = std::move(map); + + BOOST_TEST(map.empty()); + BOOST_TEST_EQ(flat_map.size(), reference_map.size()); + + BOOST_TEST_EQ(flat_map.hash_function(), hasher(1)); + BOOST_TEST_EQ(flat_map.key_eq(), key_equal(2)); + + BOOST_TEST_EQ(raii::copy_constructor, 2 * values.size()); + BOOST_TEST_EQ( + raii::destructor, 2 * values.size() + 2 * reference_map.size()); + BOOST_TEST_EQ(raii::move_constructor, 4 * reference_map.size()); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + + check_raii_counts(); + } + } // namespace // clang-format off @@ -860,6 +993,11 @@ UNORDERED_TEST( insert_and_assign, ((init_type_generator)) ((default_generator)(sequential)(limited_range))) + +UNORDERED_TEST( + flat_map_move_assign, + ((init_type_generator)) + ((default_generator)(sequential)(limited_range))) // clang-format on RUN_TESTS()