[patch] libstdc++/29988 Rb_Tree reuse allocated nodes
François Dumont
frs.dumont@gmail.com
Wed Jun 11 19:56:00 GMT 2014
For the testsuite allocator I though that for an internal allocator
used in our tests it was ok. But alright, I will make it better and
compatible with SimpleAllocator.
On 11/06/2014 14:02, Jonathan Wakely wrote:
>> Index: include/bits/stl_tree.h
>> ===================================================================
>> --- include/bits/stl_tree.h (revision 211388)
>> +++ include/bits/stl_tree.h (working copy)
>> @@ -330,6 +330,111 @@
>> const _Rb_tree_const_iterator<_Val>& __y)
>> _GLIBCXX_NOEXCEPT
>> { return __x._M_node != __y._M_node; }
>>
>> + // Functor recycling a pool of nodes and using allocation once the
>> pool is
>> + // empty.
>> + template<typename _RbTree>
>> + struct _Rb_tree_reuse_or_alloc_node
>> + {
>
> Is there a reason to define this and _Rb_tree_alloc_node as
> namespace-scope class templates, rather than non-template members of
> _Rb_tree?
Just to limit amount of code within _Rb_tree. I wanted to do
something like in _Hashtable where many code is isolated in different
types aggregated to build the final _Hashtable type. But it looks like
you prefer it nested so I will do so.
>
> They wouldn't need to be friends if they were members, and you
> wouldn't need a typedef for _Rb_tree_alloc_node<_Rb_tree> because it
> would just be called _Rb_tree_alloc_node.
>
>> + private:
>> + typedef _RbTree __rb_tree;
>
> This typedef doesn't seem useful, it's only used once and is more
> characters than "_RbTree". If the class was a member of _Rb_tree it
> could just use that name.
>
>> + typedef _Rb_tree_node<typename _RbTree::value_type> __node_type;
>
> If it was a member the value_type name would be in scope.
>
>> + public:
>> + _Rb_tree_reuse_or_alloc_node(const _Rb_tree_node_base& __header,
>> + __rb_tree& __t)
>> + : _M_root(__header._M_parent), _M_nodes(__header._M_right),
>> _M_t(__t)
>> + {
>> + if (_M_root)
>> + _M_root->_M_parent = 0;
>> + else
>> + _M_nodes = 0;
>> + }
>> +
>> + ~_Rb_tree_reuse_or_alloc_node()
>> + { _M_t._M_erase(static_cast<__node_type*>(_M_root)); }
>
> This type needs to be non-copyable, or unintentional copies would
> erase all the nodes and leave nothing to be reused (which might be
> difficult to detect as it would only affect performance, not
> correctness).
Yes, sure, like in the equivalent _Hashtable types. I guess I
didn't do so here because we might not be in c++11 so it is not as
convenient to forbid its usage.
>
>
>> + template<typename _Arg>
>> + __node_type*
>> +#if __cplusplus < 201103L
>> + operator()(const _Arg& __arg) const
>> +#else
>> + operator()(_Arg&& __arg) const
>> +#endif
>
> Does this need to be const?
>
> I don't think it does (if you change the function templates taking a
> const _NodeGen& to take _NodeGen& instead).
Sometimes I used lambdas, I am not sure but I think it forced me to take
functors as const lvalue reference and so the const qualification on the
operator.
>
> That means the members of this type don't need to be 'mutable'.
>
>
>> + typedef _Rb_tree_node_base __node_base;
>
> I'm not sure this typedef is useful either, it just means an extra
> name to remember when reading the code, when _Rb_tree_node_base is
> already in scope and probably understood by readers of the code.
>
>> + mutable __node_base* _M_root;
>> + mutable __node_base* _M_nodes;
>
> These members should be of type _Rb_tree::_Base_ptr, not __node_base*,
> because that's the type _Rb_tree::_M_right is declared as.
>
> I have a work-in-progress patch to make _Rb_tree use
> allocator_traits<_Node_allocator>::pointer for _Link_type, which
> might not be the same type as _Rb_tree_node<Val>*, so it is important
> to consistently use the _Base_ptr and _Link_type typedefs not the
> underlying types they refer to (because those underlying types are
> going to change soon).
>
>> + _RbTree& _M_t;
>> + };
>> +
>> + // Functor similar to the previous one but without any pool of
>> node to recycle.
>> + template<typename _RbTree>
>> + struct _Rb_tree_alloc_node
>
> Again, I think this should be a member of _Rb_tree.
>
>> + {
>> + private:
>> + typedef _Rb_tree_node<typename _RbTree::value_type> __node_type;
>
> This typedef should be removed.
>
>> +
>> + public:
>> + _Rb_tree_alloc_node(_RbTree& __t)
>> + : _M_t(__t) { }
>> +
>> + template<typename _Arg>
>> + __node_type*
>
> This function should return _Rb_tree::_Link_type because that's what
> _M_create_node returns.
>
>> +#if __cplusplus < 201103L
>> + operator()(const _Arg& __arg) const
>> +#else
>> + operator()(_Arg&& __arg) const
>> +#endif
>> + { return _M_t._M_create_node(_GLIBCXX_FORWARD(_Arg, __arg)); }
>> @@ -349,6 +454,12 @@
>> rebind<_Rb_tree_node<_Val> >::other _Node_allocator;
>>
>> typedef __gnu_cxx::__alloc_traits<_Node_allocator> _Alloc_traits;
>> + template<typename _RT>
>> + friend struct _Rb_tree_alloc_node;
>> + typedef _Rb_tree_alloc_node<_Rb_tree> __alloc_node_t;
>> + template<typename _RT>
>> + friend struct _Rb_tree_reuse_or_alloc_node;
>> + typedef _Rb_tree_reuse_or_alloc_node<_Rb_tree>
>> __reuse_or_alloc_node_t;
>
> These friend declarations and typedefs become unnecessary.
>
>> @@ -389,44 +500,55 @@
>> { _Alloc_traits::deallocate(_M_get_Node_allocator(), __p, 1); }
>>
>> #if __cplusplus < 201103L
>> - _Link_type
>> - _M_create_node(const value_type& __x)
>> + void
>> + _M_construct_node(_Link_type __node, const value_type& __x)
>> {
>> - _Link_type __tmp = _M_get_node();
>> __try
>> - { get_allocator().construct(__tmp->_M_valptr(), __x); }
>> + { get_allocator().construct(__node->_M_valptr(), __x); }
>> __catch(...)
>> {
>> - _M_put_node(__tmp);
>> + _M_put_node(__node);
>> __throw_exception_again;
>> }
>> + }
>> +
>> + _Link_type
>> + _M_create_node(const value_type& __x)
>> + {
>> + _Link_type __tmp = _M_get_node();
>> + _M_construct_node(__tmp, __x);
>> return __tmp;
>> }
>>
>> void
>> _M_destroy_node(_Link_type __p)
>> - {
>> - get_allocator().destroy(__p->_M_valptr());
>> - _M_put_node(__p);
>> - }
>> + { get_allocator().destroy(__p->_M_valptr()); }
>> #else
>> template<typename... _Args>
>> - _Link_type
>> - _M_create_node(_Args&&... __args)
>> + void
>> + _M_construct_node(_Link_type __node, _Args&&... __args)
>> {
>> - _Link_type __tmp = _M_get_node();
>> __try
>> {
>> - ::new(__tmp) _Rb_tree_node<_Val>;
>> + ::new(__node) _Rb_tree_node<_Val>();
>
> This should not be value-initialized.
>
> In C++11 all that does is zero out the __aligned_buffer's
> uninitialized storage, which is a waste of time as we're about to
> overwrite it anyway in the construct(). If you have a large
> value_type (e.g. std::map<int, std::array<double,1000>>) the redundant
> initialization has a measurable cost and we've had bug reports for
> similar code.
Sorry, didn't know about this one, I will revert it then.
>> @@ -514,11 +651,11 @@
>> { return this->_M_impl._M_header._M_right; }
>>
>> _Link_type
>> - _M_begin() _GLIBCXX_NOEXCEPT
>> + _M_begin() const _GLIBCXX_NOEXCEPT
>> { return
>> static_cast<_Link_type>(this->_M_impl._M_header._M_parent); }
>
> What's the purpose of this change?
> Although it can be 'const' it is consistent with the usual
> begin()/end() functions that the functions returning a mutable iterator
> are non-const and the functions returning a constant iterator are const.
>
>> _Const_Link_type
>> - _M_begin() const _GLIBCXX_NOEXCEPT
>> + _M_cbegin() const _GLIBCXX_NOEXCEPT
>> {
>> return static_cast<_Const_Link_type>
>> (this->_M_impl._M_header._M_parent);
>> @@ -529,7 +666,7 @@
>> { return reinterpret_cast<_Link_type>(&this->_M_impl._M_header); }
>>
>> _Const_Link_type
>> - _M_end() const _GLIBCXX_NOEXCEPT
>> + _M_cend() const _GLIBCXX_NOEXCEPT
>> { return
>> reinterpret_cast<_Const_Link_type>(&this->_M_impl._M_header); }
>>
>> static const_reference
>
> I'm not very comfortable with this renaming.
>
> Having consistent _M_begin() functions allows using them in template
> code that doesn't care if it's using the const or non-const version.
>
>
I will try to remember why I did those :-)
Thanks for feedback.
François
More information about the Libstdc++
mailing list