std::vector move assign patch

Marc Glisse marc.glisse@inria.fr
Sun May 14 11:26:00 GMT 2017


On Wed, 26 Apr 2017, François Dumont wrote:

> I plan to propose this patch when back to stage 1. As it defaults default and 
> move constructor I wonder if it can make any change to the generated code.

It helps quite a bit for move construction in some cases, particularly

   typedef std::vector<int> V;
   V f(V&&b){ return std::move(b); }

where gcc expects that RVO might let the return variable alias b. I don't 
know how common those cases are though.

It doesn't seem to change the generated code for move assignment. I 
thought _Vector_impl_data might make it a couple lines shorter to write 
optimal code, but that would require having an operator= (it is currently 
implicitly deleted) so it compensates.

For reference, code for slightly more efficient move assignment (not 
really tested):

       void
       _M_move_assign(vector&& __x, std::true_type) noexcept
       {
         _M_deallocate(this->_M_impl._M_start, this->_M_impl._M_end_of_storage
                       - this->_M_impl._M_start);
         this->_M_impl._M_start = __x._M_impl._M_start;
         this->_M_impl._M_finish = __x._M_impl._M_finish;
         this->_M_impl._M_end_of_storage = __x._M_impl._M_end_of_storage;
         __x._M_impl._M_start = pointer();
         __x._M_impl._M_finish = pointer();
         __x._M_impl._M_end_of_storage = pointer();
         std::__alloc_on_move(_M_get_Tp_allocator(), __x._M_get_Tp_allocator());
       }

Looking only at the code generated for

void f(V&a,V&&b){ a=std::move(b); }

it is tempting to save the arguments to _M_deallocate in temporaries and
only call it at the end: jmp instead of call on x86, less stack
manipulation.

       void
       _M_move_assign(vector&& __x, std::true_type) noexcept
       {
         pointer __ptr = this->_M_impl._M_start;
         size_t __sz = this->_M_impl._M_end_of_storage - this->_M_impl._M_start;
         this->_M_impl._M_start = __x._M_impl._M_start;
         this->_M_impl._M_finish = __x._M_impl._M_finish;
         this->_M_impl._M_end_of_storage = __x._M_impl._M_end_of_storage;
         __x._M_impl._M_start = pointer();
         __x._M_impl._M_finish = pointer();
         __x._M_impl._M_end_of_storage = pointer();
         std::__alloc_on_move(_M_get_Tp_allocator(), __x._M_get_Tp_allocator());
         _M_deallocate(__ptr, __sz);
       }

But as soon as it gets inlined in some larger function (I expect this is 
the usual case for vector's move assignment, and we probably don't care 
about performance in other cases), this advantage disappears, and it 
becomes unclear which is better.

-- 
Marc Glisse



More information about the Libstdc++ mailing list