Merge branch 'feature/foa_fast_iteration' of https://github.com/boostorg/unordered into feature/foa_fast_iteration

This commit is contained in:
joaquintides 2023-03-06 21:44:55 +01:00
commit 7faec20f26
15 changed files with 436 additions and 154 deletions

View File

@ -6,7 +6,7 @@ local library = "unordered";
local triggers = local triggers =
{ {
branch: [ "master", "develop", "feature/*", "bugfix/*" ] branch: [ "master", "develop", "feature/*", "bugfix/*", "fix/*", "pr/*" ]
}; };
local ubsan = { UBSAN: '1', UBSAN_OPTIONS: 'print_stacktrace=1' }; local ubsan = { UBSAN: '1', UBSAN_OPTIONS: 'print_stacktrace=1' };

View File

@ -75,7 +75,7 @@ jobs:
- { compiler: clang, cxxstd: '03,11,14,17,2a', os: macos-11, } - { compiler: clang, cxxstd: '03,11,14,17,2a', os: macos-11, }
- { compiler: clang, cxxstd: '03,11,14,17,2a', os: macos-12, sanitize: yes } - { compiler: clang, cxxstd: '03,11,14,17,2a', os: macos-12, sanitize: yes }
timeout-minutes: 120 timeout-minutes: 180
runs-on: ${{matrix.os}} runs-on: ${{matrix.os}}
container: ${{matrix.container}} container: ${{matrix.container}}
env: {B2_USE_CCACHE: 1} env: {B2_USE_CCACHE: 1}

View File

@ -16,6 +16,9 @@
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2363r5.html[P2363]. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2363r5.html[P2363].
* Replaced the previous post-mixing process for open-addressing containers with * Replaced the previous post-mixing process for open-addressing containers with
a new algorithm based on extended multiplication by a constant. a new algorithm based on extended multiplication by a constant.
* Fixed bug in internal emplace() impl where stack-local types were not properly
constructed using the Allocator of the container which breaks uses-allocator
construction.
== Release 1.81.0 - Major update == Release 1.81.0 - Major update

View File

@ -1186,6 +1186,14 @@ _STL_RESTORE_DEPRECATED_WARNING
*/ */
constexpr static float const mlf = 0.875f; constexpr static float const mlf = 0.875f;
template <class T>
union uninitialized_storage
{
T t_;
uninitialized_storage(){}
~uninitialized_storage(){}
};
/* foa::table interface departs in a number of ways from that of C++ unordered /* foa::table interface departs in a number of ways from that of C++ unordered
* associative containers because it's not for end-user consumption * associative containers because it's not for end-user consumption
* (boost::unordered_[flat|node]_[map|set]) wrappers complete it as * (boost::unordered_[flat|node]_[map|set]) wrappers complete it as
@ -1220,11 +1228,17 @@ constexpr static float const mlf = 0.875f;
* a copyable const std::string&&. foa::table::insert is extended to accept * a copyable const std::string&&. foa::table::insert is extended to accept
* both init_type and value_type references. * both init_type and value_type references.
* *
* - TypePolicy::move(element_type&) returns a temporary object for value * - TypePolicy::construct and TypePolicy::destroy are used for the
* transfer on rehashing, move copy/assignment, and merge. For flat map, this * construction and destruction of the internal types: value_type, init_type
* object is a std::pair<Key&&,T&&>, which is generally cheaper to move * and element_type.
* than std::pair<const Key,T>&& because of the constness in Key. For *
* node-based tables, this is used to transfer ownership of pointer. * - TypePolicy::move is used to provide move semantics for the internal
* types used by the container during rehashing and emplace. These types
* are init_type, value_type and emplace_type. During insertion, a
* stack-local type will be created based on the constructibility of the
* value_type and the supplied arguments. TypePolicy::move is used here
* for transfer of ownership. Similarly, TypePolicy::move is also used
* during rehashing when elements are moved to the new table.
* *
* - TypePolicy::extract returns a const reference to the key part of * - TypePolicy::extract returns a const reference to the key part of
* a value of type value_type, init_type, element_type or * a value of type value_type, init_type, element_type or
@ -1475,18 +1489,25 @@ public:
template<typename... Args> template<typename... Args>
BOOST_FORCEINLINE std::pair<iterator,bool> emplace(Args&&... args) BOOST_FORCEINLINE std::pair<iterator,bool> emplace(Args&&... args)
{ {
/* We dispatch based on whether or not the value_type is constructible from using emplace_type=typename std::conditional<
* an rvalue reference of the deduced emplace_type. We do this specifically std::is_constructible<init_type,Args...>::value,
* for the case of the node-based containers. To this end, we're able to init_type,
* avoid allocating a node when a duplicate element is attempted to be value_type
* inserted. For immovable types, we instead dispatch to the routine that >::type;
* unconditionally allocates via `type_policy::construct()`.
*/ using insert_type=typename std::conditional<
return emplace_value(
std::is_constructible< std::is_constructible<
value_type, value_type,emplace_type>::value,
emplace_type<Args...>&&>{}, emplace_type,element_type
std::forward<Args>(args)...); >::type;
uninitialized_storage<insert_type> s;
auto *p=std::addressof(s.t_);
type_policy::construct(al(),p,std::forward<Args>(args)...);
destroy_on_exit<insert_type> guard{al(),p};
return emplace_impl(type_policy::move(*p));
} }
template<typename Key,typename... Args> template<typename Key,typename... Args>
@ -1671,15 +1692,6 @@ private:
template<typename,typename,typename,typename> friend class table; template<typename,typename,typename,typename> friend class table;
using arrays_type=table_arrays<element_type,group_type,size_policy>; using arrays_type=table_arrays<element_type,group_type,size_policy>;
template<typename... Args>
using emplace_type = typename std::conditional<
std::is_constructible<
init_type,Args...
>::value,
init_type,
value_type
>::type;
struct clear_on_exit struct clear_on_exit
{ {
~clear_on_exit(){x.clear();} ~clear_on_exit(){x.clear();}
@ -1698,6 +1710,14 @@ private:
bool rollback_=false; bool rollback_=false;
}; };
template <class T>
struct destroy_on_exit
{
Allocator &a;
T *p;
~destroy_on_exit(){type_policy::destroy(a,p);};
};
Hash& h(){return hash_base::get();} Hash& h(){return hash_base::get();}
const Hash& h()const{return hash_base::get();} const Hash& h()const{return hash_base::get();}
Pred& pred(){return pred_base::get();} Pred& pred(){return pred_base::get();}
@ -1961,29 +1981,6 @@ private:
#pragma warning(pop) /* C4800 */ #pragma warning(pop) /* C4800 */
#endif #endif
template<typename... Args>
BOOST_FORCEINLINE std::pair<iterator,bool> emplace_value(
std::true_type /* movable value_type */,Args&&... args
) {
using emplace_type_t = emplace_type<Args...>;
return emplace_impl(emplace_type_t(std::forward<Args>(args)...));
}
template<typename... Args>
BOOST_FORCEINLINE std::pair<iterator,bool> emplace_value(
std::false_type /* immovable value_type */,Args&&... args
) {
alignas(element_type)
unsigned char buf[sizeof(element_type)];
element_type* p = reinterpret_cast<element_type*>(buf);
type_policy::construct(al(),p,std::forward<Args>(args)...);
destroy_element_on_exit d{this,p};
(void)d;
return emplace_impl(type_policy::move(*p));
}
template<typename... Args> template<typename... Args>
BOOST_FORCEINLINE std::pair<iterator,bool> emplace_impl(Args&&... args) BOOST_FORCEINLINE std::pair<iterator,bool> emplace_impl(Args&&... args)
{ {

View File

@ -0,0 +1,60 @@
/* Copyright 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)
*
* See https://www.boost.org/libs/unordered for library home page.
*/
#ifndef BOOST_UNORDERED_DETAIL_FOA_ELEMENT_TYPE_HPP
#define BOOST_UNORDERED_DETAIL_FOA_ELEMENT_TYPE_HPP
namespace boost{
namespace unordered{
namespace detail{
namespace foa{
template<class T>
struct element_type
{
using value_type=T;
value_type* p;
/*
* we use a deleted copy constructor here so the type is no longer
* trivially copy-constructible which inhibits our memcpy
* optimizations when copying the tables
*/
element_type() = default;
element_type(value_type* p_):p(p_){}
element_type(element_type const&) = delete;
element_type(element_type&& rhs) noexcept
{
p = rhs.p;
rhs.p = nullptr;
}
element_type& operator=(element_type const&)=delete;
element_type& operator=(element_type&& rhs)noexcept
{
if (this!=&rhs){
p=rhs.p;
rhs.p=nullptr;
}
return *this;
}
void swap(element_type& rhs)noexcept
{
auto tmp=p;
p=rhs.p;
rhs.p=tmp;
}
};
}
}
}
}
#endif // BOOST_UNORDERED_DETAIL_FOA_ELEMENT_TYPE_HPP

View File

@ -45,20 +45,30 @@ struct node_handle_base
private: private:
using node_value_type=typename type_policy::value_type; using node_value_type=typename type_policy::value_type;
node_value_type* p_=nullptr; element_type p_;
BOOST_ATTRIBUTE_NO_UNIQUE_ADDRESS opt_storage<Allocator> a_; BOOST_ATTRIBUTE_NO_UNIQUE_ADDRESS opt_storage<Allocator> a_;
protected: protected:
node_value_type& element()noexcept node_value_type& data()noexcept
{ {
BOOST_ASSERT(!empty()); return *(p_.p);
return *p_;
} }
node_value_type const& element()const noexcept node_value_type const& data()const noexcept
{
return *(p_.p);
}
element_type& element()noexcept
{ {
BOOST_ASSERT(!empty()); BOOST_ASSERT(!empty());
return *p_; return p_;
}
element_type const& element()const noexcept
{
BOOST_ASSERT(!empty());
return p_;
} }
Allocator& al()noexcept Allocator& al()noexcept
@ -73,32 +83,29 @@ struct node_handle_base
return a_.t_; return a_.t_;
} }
void emplace(node_value_type* p,Allocator a)
{
BOOST_ASSERT(empty());
p_=p;
new(&a_.t_)Allocator(a);
}
void emplace(element_type&& x,Allocator a) void emplace(element_type&& x,Allocator a)
{ {
emplace(x.p,a); BOOST_ASSERT(empty());
auto* p=x.p;
p_.p=p;
new(&a_.t_)Allocator(a);
x.p=nullptr; x.p=nullptr;
} }
void reset() void reset()
{ {
al().~Allocator(); a_.t_.~Allocator();
p_=nullptr; p_.p=nullptr;
} }
public: public:
constexpr node_handle_base()noexcept{} constexpr node_handle_base()noexcept:p_{nullptr}{}
node_handle_base(node_handle_base&& nh) noexcept node_handle_base(node_handle_base&& nh) noexcept
{ {
p_.p = nullptr;
if (!nh.empty()){ if (!nh.empty()){
emplace(nh.p_,nh.al()); emplace(std::move(nh.p_),nh.al());
nh.reset(); nh.reset();
} }
} }
@ -110,12 +117,12 @@ struct node_handle_base
if(nh.empty()){ /* empty(), nh.empty() */ if(nh.empty()){ /* empty(), nh.empty() */
/* nothing to do */ /* nothing to do */
}else{ /* empty(), !nh.empty() */ }else{ /* empty(), !nh.empty() */
emplace(nh.p_,std::move(nh.al())); emplace(std::move(nh.p_),std::move(nh.al()));
nh.reset(); nh.reset();
} }
}else{ }else{
if(nh.empty()){ /* !empty(), nh.empty() */ if(nh.empty()){ /* !empty(), nh.empty() */
type_policy::destroy(al(),p_); type_policy::destroy(al(),&p_);
reset(); reset();
}else{ /* !empty(), !nh.empty() */ }else{ /* !empty(), !nh.empty() */
bool const pocma= bool const pocma=
@ -124,12 +131,12 @@ struct node_handle_base
BOOST_ASSERT(pocma||al()==nh.al()); BOOST_ASSERT(pocma||al()==nh.al());
type_policy::destroy(al(),p_); type_policy::destroy(al(),&p_);
if(pocma){ if(pocma){
al()=std::move(nh.al()); al()=std::move(nh.al());
} }
p_=nh.p_; p_=std::move(nh.p_);
nh.reset(); nh.reset();
} }
} }
@ -137,7 +144,7 @@ struct node_handle_base
if(empty()){ /* empty(), nh.empty() */ if(empty()){ /* empty(), nh.empty() */
/* nothing to do */ /* nothing to do */
}else{ /* !empty(), !nh.empty() */ }else{ /* !empty(), !nh.empty() */
type_policy::destroy(al(),p_); type_policy::destroy(al(),&p_);
reset(); reset();
} }
} }
@ -147,14 +154,14 @@ struct node_handle_base
~node_handle_base() ~node_handle_base()
{ {
if(!empty()){ if(!empty()){
type_policy::destroy(al(),p_); type_policy::destroy(al(),&p_);
reset(); reset();
} }
} }
allocator_type get_allocator()const noexcept{return al();} allocator_type get_allocator()const noexcept{return al();}
explicit operator bool()const noexcept{ return !empty();} explicit operator bool()const noexcept{ return !empty();}
BOOST_ATTRIBUTE_NODISCARD bool empty()const noexcept{return p_==nullptr;} BOOST_ATTRIBUTE_NODISCARD bool empty()const noexcept{return p_.p==nullptr;}
void swap(node_handle_base& nh) noexcept( void swap(node_handle_base& nh) noexcept(
boost::allocator_is_always_equal<Allocator>::type::value|| boost::allocator_is_always_equal<Allocator>::type::value||
@ -165,12 +172,12 @@ struct node_handle_base
if(nh.empty()) { if(nh.empty()) {
/* nothing to do here */ /* nothing to do here */
} else { } else {
emplace(nh.p_, nh.al()); emplace(std::move(nh.p_), nh.al());
nh.reset(); nh.reset();
} }
}else{ }else{
if(nh.empty()){ if(nh.empty()){
nh.emplace(p_,al()); nh.emplace(std::move(p_),al());
reset(); reset();
}else{ }else{
bool const pocs= bool const pocs=
@ -180,7 +187,7 @@ struct node_handle_base
BOOST_ASSERT(pocs || al()==nh.al()); BOOST_ASSERT(pocs || al()==nh.al());
using std::swap; using std::swap;
swap(p_,nh.p_); p_.swap(nh.p_);
if(pocs)swap(al(),nh.al()); if(pocs)swap(al(),nh.al());
} }
} }

View File

@ -0,0 +1,21 @@
#ifndef BOOST_UNORDERED_DETAIL_REQUIRES_CXX11_HPP_INCLUDED
#define BOOST_UNORDERED_DETAIL_REQUIRES_CXX11_HPP_INCLUDED
// Copyright 2023 Peter Dimov
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
#include <boost/config.hpp>
#include <boost/config/pragma_message.hpp>
#if defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) || \
defined(BOOST_NO_CXX11_RVALUE_REFERENCES) || \
defined(BOOST_NO_CXX11_DECLTYPE) || \
defined(BOOST_NO_CXX11_CONSTEXPR) || \
defined(BOOST_NO_CXX11_NOEXCEPT)
BOOST_PRAGMA_MESSAGE("C++03 support is deprecated in Boost.Unordered 1.82 and will be removed in Boost.Unordered 1.84.")
#endif
#endif // #ifndef BOOST_UNORDERED_DETAIL_REQUIRES_CXX11_HPP_INCLUDED

View File

@ -53,6 +53,11 @@ namespace boost {
return kv.first; return kv.first;
} }
static moved_type move(init_type& x)
{
return {std::move(x.first), std::move(x.second)};
}
static moved_type move(element_type& x) static moved_type move(element_type& x)
{ {
// TODO: we probably need to launder here // TODO: we probably need to launder here
@ -61,12 +66,23 @@ namespace boost {
} }
template <class A, class... Args> template <class A, class... Args>
static void construct(A& al, element_type* p, Args&&... args) static void construct(A& al, init_type* p, Args&&... args)
{ {
boost::allocator_construct(al, p, std::forward<Args>(args)...); boost::allocator_construct(al, p, std::forward<Args>(args)...);
} }
template <class A> static void destroy(A& al, element_type* p) noexcept template <class A, class... Args>
static void construct(A& al, value_type* p, Args&&... args)
{
boost::allocator_construct(al, p, std::forward<Args>(args)...);
}
template <class A> static void destroy(A& al, init_type* p) noexcept
{
boost::allocator_destroy(al, p);
}
template <class A> static void destroy(A& al, value_type* p) noexcept
{ {
boost::allocator_destroy(al, p); boost::allocator_destroy(al, p);
} }

View File

@ -46,12 +46,12 @@ namespace boost {
static element_type&& move(element_type& x) { return std::move(x); } static element_type&& move(element_type& x) { return std::move(x); }
template <class A, class... Args> template <class A, class... Args>
static void construct(A& al, element_type* p, Args&&... args) static void construct(A& al, value_type* p, Args&&... args)
{ {
boost::allocator_construct(al, p, std::forward<Args>(args)...); boost::allocator_construct(al, p, std::forward<Args>(args)...);
} }
template <class A> static void destroy(A& al, element_type* p) noexcept template <class A> static void destroy(A& al, value_type* p) noexcept
{ {
boost::allocator_destroy(al, p); boost::allocator_destroy(al, p);
} }

View File

@ -15,6 +15,7 @@
#pragma once #pragma once
#endif #endif
#include <boost/unordered/detail/requires_cxx11.hpp>
#include <boost/core/explicit_operator_bool.hpp> #include <boost/core/explicit_operator_bool.hpp>
#include <boost/functional/hash.hpp> #include <boost/functional/hash.hpp>
#include <boost/move/move.hpp> #include <boost/move/move.hpp>

View File

@ -11,6 +11,7 @@
#endif #endif
#include <boost/unordered/detail/foa.hpp> #include <boost/unordered/detail/foa.hpp>
#include <boost/unordered/detail/foa/element_type.hpp>
#include <boost/unordered/detail/foa/node_handle.hpp> #include <boost/unordered/detail/foa/node_handle.hpp>
#include <boost/unordered/detail/type_traits.hpp> #include <boost/unordered/detail/type_traits.hpp>
#include <boost/unordered/unordered_node_map_fwd.hpp> #include <boost/unordered/unordered_node_map_fwd.hpp>
@ -45,23 +46,7 @@ namespace boost {
using value_type = std::pair<Key const, T>; using value_type = std::pair<Key const, T>;
using moved_type = std::pair<raw_key_type&&, raw_mapped_type&&>; using moved_type = std::pair<raw_key_type&&, raw_mapped_type&&>;
struct element_type using element_type=foa::element_type<value_type>;
{
value_type* p;
/*
* we use a deleted copy constructor here so the type is no longer
* trivially copy-constructible which inhibits our memcpy
* optimizations when copying the tables
*/
element_type() = default;
element_type(element_type const&) = delete;
element_type(element_type&& rhs) noexcept
{
p = rhs.p;
rhs.p = nullptr;
}
};
static value_type& value_from(element_type const& x) { return *(x.p); } static value_type& value_from(element_type const& x) { return *(x.p); }
@ -77,6 +62,11 @@ namespace boost {
} }
static element_type&& move(element_type& x) { return std::move(x); } static element_type&& move(element_type& x) { return std::move(x); }
static moved_type move(init_type& x)
{
return {std::move(x.first), std::move(x.second)};
}
static moved_type move(value_type& x) static moved_type move(value_type& x)
{ {
return {std::move(const_cast<raw_key_type&>(x.first)), return {std::move(const_cast<raw_key_type&>(x.first)),
@ -96,6 +86,18 @@ namespace boost {
construct(al, p, *copy.p); construct(al, p, *copy.p);
} }
template <class A, class... Args>
static void construct(A& al, init_type* p, Args&&... args)
{
boost::allocator_construct(al, p, std::forward<Args>(args)...);
}
template <class A, class... Args>
static void construct(A& al, value_type* p, Args&&... args)
{
boost::allocator_construct(al, p, std::forward<Args>(args)...);
}
template <class A, class... Args> template <class A, class... Args>
static void construct(A& al, element_type* p, Args&&... args) static void construct(A& al, element_type* p, Args&&... args)
{ {
@ -106,10 +108,11 @@ namespace boost {
} }
BOOST_CATCH(...) BOOST_CATCH(...)
{ {
boost::allocator_deallocate(al, using pointer_type = typename boost::allocator_pointer<A>::type;
boost::pointer_traits< using pointer_traits = boost::pointer_traits<pointer_type>;
typename boost::allocator_pointer<A>::type>::pointer_to(*p->p),
1); boost::allocator_deallocate(
al, pointer_traits::pointer_to(*(p->p)), 1);
BOOST_RETHROW BOOST_RETHROW
} }
BOOST_CATCH_END BOOST_CATCH_END
@ -118,16 +121,22 @@ namespace boost {
template <class A> static void destroy(A& al, value_type* p) noexcept template <class A> static void destroy(A& al, value_type* p) noexcept
{ {
boost::allocator_destroy(al, p); boost::allocator_destroy(al, p);
boost::allocator_deallocate(al, }
boost::pointer_traits<
typename boost::allocator_pointer<A>::type>::pointer_to(*p), template <class A> static void destroy(A& al, init_type* p) noexcept
1); {
boost::allocator_destroy(al, p);
} }
template <class A> static void destroy(A& al, element_type* p) noexcept template <class A> static void destroy(A& al, element_type* p) noexcept
{ {
if (p->p) { if (p->p) {
destroy(al,p->p); using pointer_type = typename boost::allocator_pointer<A>::type;
using pointer_traits = boost::pointer_traits<pointer_type>;
destroy(al, p->p);
boost::allocator_deallocate(
al, pointer_traits::pointer_to(*(p->p)), 1);
} }
} }
}; };
@ -137,8 +146,7 @@ namespace boost {
: public detail::foa::node_handle_base<TypePolicy, Allocator> : public detail::foa::node_handle_base<TypePolicy, Allocator>
{ {
private: private:
using base_type = using base_type = detail::foa::node_handle_base<TypePolicy, Allocator>;
detail::foa::node_handle_base<TypePolicy, Allocator>;
using typename base_type::type_policy; using typename base_type::type_policy;
@ -157,13 +165,13 @@ namespace boost {
key_type& key() const key_type& key() const
{ {
BOOST_ASSERT(!this->empty()); BOOST_ASSERT(!this->empty());
return const_cast<key_type&>(this->element().first); return const_cast<key_type&>(this->data().first);
} }
mapped_type& mapped() const mapped_type& mapped() const
{ {
BOOST_ASSERT(!this->empty()); BOOST_ASSERT(!this->empty());
return const_cast<mapped_type&>(this->element().second); return const_cast<mapped_type&>(this->data().second);
} }
}; };
} // namespace detail } // namespace detail
@ -402,10 +410,7 @@ namespace boost {
BOOST_ASSERT(get_allocator() == nh.get_allocator()); BOOST_ASSERT(get_allocator() == nh.get_allocator());
typename map_types::element_type x; auto itp = table_.insert(std::move(nh.element()));
x.p = std::addressof(nh.element());
auto itp = table_.insert(std::move(x));
if (itp.second) { if (itp.second) {
nh.reset(); nh.reset();
return {itp.first, true, node_type{}}; return {itp.first, true, node_type{}};
@ -422,10 +427,7 @@ namespace boost {
BOOST_ASSERT(get_allocator() == nh.get_allocator()); BOOST_ASSERT(get_allocator() == nh.get_allocator());
typename map_types::element_type x; auto itp = table_.insert(std::move(nh.element()));
x.p = std::addressof(nh.element());
auto itp = table_.insert(std::move(x));
if (itp.second) { if (itp.second) {
nh.reset(); nh.reset();
return itp.first; return itp.first;

View File

@ -11,6 +11,7 @@
#endif #endif
#include <boost/unordered/detail/foa.hpp> #include <boost/unordered/detail/foa.hpp>
#include <boost/unordered/detail/foa/element_type.hpp>
#include <boost/unordered/detail/foa/node_handle.hpp> #include <boost/unordered/detail/foa/node_handle.hpp>
#include <boost/unordered/detail/type_traits.hpp> #include <boost/unordered/detail/type_traits.hpp>
#include <boost/unordered/unordered_node_set_fwd.hpp> #include <boost/unordered/unordered_node_set_fwd.hpp>
@ -41,23 +42,7 @@ namespace boost {
static Key const& extract(value_type const& key) { return key; } static Key const& extract(value_type const& key) { return key; }
struct element_type using element_type=foa::element_type<value_type>;
{
value_type* p;
/*
* we use a deleted copy constructor here so the type is no longer
* trivially copy-constructible which inhibits our memcpy
* optimizations when copying the tables
*/
element_type() = default;
element_type(element_type const&) = delete;
element_type(element_type&& rhs) noexcept
{
p = rhs.p;
rhs.p = nullptr;
}
};
static value_type& value_from(element_type const& x) { return *x.p; } static value_type& value_from(element_type const& x) { return *x.p; }
static Key const& extract(element_type const& k) { return *k.p; } static Key const& extract(element_type const& k) { return *k.p; }
@ -78,6 +63,12 @@ namespace boost {
x.p = nullptr; x.p = nullptr;
} }
template <class A, class... Args>
static void construct(A& al, value_type* p, Args&&... args)
{
boost::allocator_construct(al, p, std::forward<Args>(args)...);
}
template <class A, class... Args> template <class A, class... Args>
static void construct(A& al, element_type* p, Args&&... args) static void construct(A& al, element_type* p, Args&&... args)
{ {
@ -100,16 +91,16 @@ namespace boost {
template <class A> static void destroy(A& al, value_type* p) noexcept template <class A> static void destroy(A& al, value_type* p) noexcept
{ {
boost::allocator_destroy(al, p); boost::allocator_destroy(al, p);
boost::allocator_deallocate(al,
boost::pointer_traits<
typename boost::allocator_pointer<A>::type>::pointer_to(*p),
1);
} }
template <class A> static void destroy(A& al, element_type* p) noexcept template <class A> static void destroy(A& al, element_type* p) noexcept
{ {
if (p->p) { if (p->p) {
destroy(al, p->p); destroy(al, p->p);
boost::allocator_deallocate(al,
boost::pointer_traits<typename boost::allocator_pointer<
A>::type>::pointer_to(*(p->p)),
1);
} }
} }
}; };
@ -119,8 +110,7 @@ namespace boost {
: public detail::foa::node_handle_base<TypePolicy, Allocator> : public detail::foa::node_handle_base<TypePolicy, Allocator>
{ {
private: private:
using base_type = using base_type = detail::foa::node_handle_base<TypePolicy, Allocator>;
detail::foa::node_handle_base<TypePolicy, Allocator>;
using typename base_type::type_policy; using typename base_type::type_policy;
@ -137,7 +127,7 @@ namespace boost {
value_type& value() const value_type& value() const
{ {
BOOST_ASSERT(!this->empty()); BOOST_ASSERT(!this->empty());
return const_cast<value_type&>(this->element()); return const_cast<value_type&>(this->data());
} }
}; };
} // namespace detail } // namespace detail
@ -390,10 +380,7 @@ namespace boost {
BOOST_ASSERT(get_allocator() == nh.get_allocator()); BOOST_ASSERT(get_allocator() == nh.get_allocator());
typename set_types::element_type x; auto itp = table_.insert(std::move(nh.element()));
x.p=std::addressof(nh.element());
auto itp = table_.insert(std::move(x));
if (itp.second) { if (itp.second) {
nh.reset(); nh.reset();
return {itp.first, true, node_type{}}; return {itp.first, true, node_type{}};
@ -410,10 +397,7 @@ namespace boost {
BOOST_ASSERT(get_allocator() == nh.get_allocator()); BOOST_ASSERT(get_allocator() == nh.get_allocator());
typename set_types::element_type x; auto itp = table_.insert(std::move(nh.element()));
x.p=std::addressof(nh.element());
auto itp = table_.insert(std::move(x));
if (itp.second) { if (itp.second) {
nh.reset(); nh.reset();
return itp.first; return itp.first;
@ -478,7 +462,7 @@ namespace boost {
node_type extract(key_type const& key) node_type extract(key_type const& key)
{ {
auto pos = find(key); auto pos = find(key);
return pos!=end()?extract(pos):node_type(); return pos != end() ? extract(pos) : node_type();
} }
template <class K> template <class K>
@ -489,7 +473,7 @@ namespace boost {
extract(K const& key) extract(K const& key)
{ {
auto pos = find(key); auto pos = find(key);
return pos!=end()?extract(pos):node_type(); return pos != end() ? extract(pos) : node_type();
} }
template <class H2, class P2> template <class H2, class P2>

View File

@ -15,6 +15,7 @@
#pragma once #pragma once
#endif #endif
#include <boost/unordered/detail/requires_cxx11.hpp>
#include <boost/core/explicit_operator_bool.hpp> #include <boost/core/explicit_operator_bool.hpp>
#include <boost/functional/hash.hpp> #include <boost/functional/hash.hpp>
#include <boost/move/move.hpp> #include <boost/move/move.hpp>

View File

@ -144,6 +144,7 @@ build_foa init_type_insert_tests ;
build_foa max_load_tests ; build_foa max_load_tests ;
build_foa extract_tests ; build_foa extract_tests ;
build_foa node_handle_tests ; build_foa node_handle_tests ;
build_foa uses_allocator ;
run unordered/hash_is_avalanching_test.cpp ; run unordered/hash_is_avalanching_test.cpp ;

View File

@ -0,0 +1,189 @@
// Copyright 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/test.hpp"
#include <boost/unordered/detail/implementation.hpp>
#include <boost/unordered/unordered_flat_map.hpp>
#include <boost/unordered/unordered_flat_set.hpp>
#include <boost/unordered/unordered_node_map.hpp>
#include <boost/unordered/unordered_node_set.hpp>
#include <boost/config.hpp>
#include <boost/config/pragma_message.hpp>
#include <boost/config/workaround.hpp>
#if BOOST_CXX_VERSION <= 199711L || \
BOOST_WORKAROUND(BOOST_GCC_VERSION, < 40800) || \
(defined(BOOST_LIBSTDCXX_VERSION) && BOOST_CXX_VERSION > 201703L) || \
(defined(BOOST_MSVC_FULL_VER) && BOOST_MSVC_FULL_VER >= 192000000 && \
BOOST_MSVC_FULL_VER < 193000000)
// automatically disable this test for C++03 builds so we can use the STL's
// scoped_allocator_adaptor
// we remove C++20 support for libstdc++ builds because of:
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108952
//
// msvc-14.2 w/ C++20 is similarly affected
//
BOOST_PRAGMA_MESSAGE("uses_allocator tests require C++11, scoped_allocator")
int main() {}
#else
#include <memory>
#include <scoped_allocator>
#include <unordered_map>
#include <vector>
template <class T> struct allocator
{
typedef T value_type;
int tag_ = -1;
allocator() = default;
allocator(int tag) : tag_{tag} {}
allocator(allocator const&) = default;
allocator(allocator&&) = default;
template <class U> allocator(allocator<U> const& rhs) : tag_{rhs.tag_} {}
BOOST_ATTRIBUTE_NODISCARD T* allocate(std::size_t n)
{
return static_cast<T*>(::operator new(n * sizeof(T)));
}
void deallocate(T* p, std::size_t) noexcept { ::operator delete(p); }
allocator& operator=(allocator const& rhs)
{
tag_ = rhs.tag_;
return *this;
}
allocator& operator=(allocator&& rhs) noexcept
{
tag_ = rhs.tag_;
return *this;
}
bool operator==(allocator const&) const { return true; }
bool operator!=(allocator const&) const { return false; }
};
struct raii_tracker
{
static int count;
static int copy_count;
static int move_count;
static int alloc_move_count;
using allocator_type = allocator<int>;
allocator_type a_;
raii_tracker(allocator_type a) : a_(a) { ++count; }
raii_tracker(int, allocator_type const& a) : a_(a) { ++count; }
raii_tracker(raii_tracker const&) { ++copy_count; }
raii_tracker(raii_tracker&&) noexcept { ++move_count; }
raii_tracker(raii_tracker&&, allocator_type const& a) noexcept : a_(a)
{
++alloc_move_count;
}
allocator_type get_allocator() const noexcept { return a_; }
friend bool operator==(raii_tracker const&, raii_tracker const&)
{
return true;
}
};
int raii_tracker::count = 0;
int raii_tracker::copy_count = 0;
int raii_tracker::move_count = 0;
int raii_tracker::alloc_move_count = 0;
static void reset_counts()
{
raii_tracker::count = 0;
raii_tracker::copy_count = 0;
raii_tracker::move_count = 0;
raii_tracker::alloc_move_count = 0;
}
std::size_t hash_value(raii_tracker const&) { return 0; }
using map_allocator_type = std::scoped_allocator_adaptor<
allocator<std::pair<raii_tracker const, raii_tracker> >, allocator<int> >;
using set_allocator_type =
std::scoped_allocator_adaptor<allocator<raii_tracker>, allocator<int> >;
using map_type = boost::unordered_flat_map<raii_tracker, raii_tracker,
boost::hash<raii_tracker>, std::equal_to<raii_tracker>, map_allocator_type>;
using node_map_type = boost::unordered_node_map<raii_tracker, raii_tracker,
boost::hash<raii_tracker>, std::equal_to<raii_tracker>, map_allocator_type>;
using set_type = boost::unordered_flat_set<raii_tracker,
boost::hash<raii_tracker>, std::equal_to<raii_tracker>, set_allocator_type>;
using node_set_type = boost::unordered_node_set<raii_tracker,
boost::hash<raii_tracker>, std::equal_to<raii_tracker>, set_allocator_type>;
map_type* flat_map;
node_map_type* node_map;
set_type* flat_set;
node_set_type* node_set;
template <class X> static void map_uses_allocator_construction(X*)
{
reset_counts();
map_allocator_type alloc(
allocator<std::pair<raii_tracker const, raii_tracker> >{12},
allocator<int>{34});
X map(1, alloc);
map.emplace(
std::piecewise_construct, std::make_tuple(1337), std::make_tuple(7331));
BOOST_TEST_EQ(raii_tracker::count, 2);
BOOST_TEST_EQ(raii_tracker::move_count, 0);
BOOST_TEST_EQ(raii_tracker::alloc_move_count, 2);
BOOST_TEST_EQ(map.begin()->first.get_allocator().tag_, 34);
BOOST_TEST_EQ(map.begin()->second.get_allocator().tag_, 34);
}
template <class X> static void set_uses_allocator_construction(X*)
{
reset_counts();
set_allocator_type alloc(allocator<raii_tracker>{12}, allocator<int>{34});
X set(1, alloc);
set.emplace();
BOOST_TEST_EQ(raii_tracker::count, 1);
BOOST_TEST_EQ(raii_tracker::move_count, 0);
BOOST_TEST_EQ(raii_tracker::alloc_move_count, 1);
BOOST_TEST_EQ(set.begin()->get_allocator().tag_, 34);
}
UNORDERED_TEST(map_uses_allocator_construction, ((flat_map)(node_map)))
UNORDERED_TEST(set_uses_allocator_construction, ((flat_set)(node_set)))
RUN_TESTS()
#endif