[patch] libstdc++/29988 Rb_Tree reuse allocated nodes
Jonathan Wakely
jwakely@redhat.com
Wed Jun 11 12:02:00 GMT 2014
>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?
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).
>+ 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).
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.
>@@ -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.
More information about the Libstdc++
mailing list