#1910 closed Bugs (fixed)
function::swap should avoid doing memory allocations
Reported by: | niels_dekker | Owned by: | Douglas Gregor |
---|---|---|---|
Milestone: | Boost 1.36.0 | Component: | function |
Version: | Boost 1.35.0 | Severity: | Problem |
Keywords: | Cc: | nd_mail_address_valid_until_2008-12-31@… |
Description
This ticket was triggered by a posting from Arvid Norberg, boost::function<>::swap() can throw. He was surprised to see that boost::function::swap
can throw an exception, in case of a memory allocation failure. And he also remarked that TR1's function::swap
is specified not to throw.
The current function::swap
(function_template.hpp, from trunk revision 43884) is implemented by doing three copy operations:
void swap(BOOST_FUNCTION_FUNCTION& other) { if (&other == this) return; BOOST_FUNCTION_FUNCTION tmp = *this; *this = other; other = tmp; }
I think that a major cause of exceptions can be taken away, by having function::swap
implemented by means of moving, instead of copying:
void swap(BOOST_FUNCTION_FUNCTION& other) { if (&other == this) return; BOOST_FUNCTION_FUNCTION tmp; tmp.move_assign(*this); this->move_assign(other); other.move_assign(tmp); }
This new boost::function::move_assign
member function would in most cases just copy the function object held by its argument to *this
, just like boost::function
's copy assignment operator does. But if the argument would hold its function object within a buffer allocated on the heap, move_assign
would pass the buffer to *this
, and set the argument's buffer pointer to NULL
.
The patch has boost::function::move_assign
added to function_template.hpp
.
Out of modesty, I declared move_assign
as a private member function, but feel free to make it public. Moreover, it could be changed to move from *this
to its argument (and renamed, e.g., to move_to
), so that it would support moving a temporary (rvalue). But I think that's beyond the scope of this ticket...
Implementation details
The attached patch has a boolean argument added to all manage
functions, telling whether or not the buffer should be moved. Thereby, the implementation of move_assign
is quite similar to the existing assign_to_own
, having the same call to f.vtable->manager
, but passing true
as last argument.
This boolean argument move_buffer
is only relevant to the specific manager
overload for function objects that require heap allocation, which does in the patched version of function_base.hpp
:
if (move_buffer) { out_buffer.obj_ptr = in_buffer.obj_ptr; in_buffer.obj_ptr = 0; }
I made obj_ptr
mutable, to work around the fact that in_buffer
is a const reference. Instead, I guess a const_cast
could be used, but I didn't have the guts to do so. ;-)
Attachments (2)
Change History (10)
by , 14 years ago
Attachment: | moving_function_swap.patch added |
---|
comment:1 by , 14 years ago
Cc: | added |
---|
comment:2 by , 14 years ago
follow-up: 5 comment:3 by , 14 years ago
Resolution: | → fixed |
---|---|
Status: | new → closed |
comment:4 by , 14 years ago
Thank you, Doug! I'm not sure if we should sacrifice the small-object optimization to get a strictly nothrow swap. For the time being, the function::swap performance improvement you've just committed looks like a reasonable compromise to me.
comment:5 by , 14 years ago
Replying to dgregor:
(In [48615]) Improve the performance of Boost.Function's swap.
Sorry, I'm not yet entirely sure about your fix of function_base.hpp, as it says in manage_small:
if (op == move_functor_tag) { reinterpret_cast<functor_type*>(&in_buffer.data)->~Functor(); }
As a consequence, it seems to me that the functor may be destructed too many times. The following BOOST_CHECK fails on my computer:
static unsigned construction_count; static unsigned destruction_count; struct MyFunctor { MyFunctor() { ++construction_count; } MyFunctor(const MyFunctor &) { ++construction_count; } ~MyFunctor() { ++destruction_count; } int operator()() { return 0; } }; int test_main(int, char* []) { { boost::function0<int> f; boost::function0<int> g; f = MyFunctor(); g = MyFunctor(); f.swap(g); } // MyFunctor objects should be constructed at least // as many times as they are destructed. BOOST_CHECK(construction_count >= destruction_count); }
comment:6 by , 14 years ago
comment:7 by , 14 years ago
Now that boost::function internally has a move_assign function, I think that the performance of its assignment operators can also be improved significantly, by calling move_assign, instead of swap. Please have a look at the attached function_assignment.patch
by , 14 years ago
Attachment: | function_assignment.patch added |
---|
boost::function assignment operators calling move_assign. (Update: the patch now has a by-value argument for the copy assignment operator.)
comment:8 by , 14 years ago
Please note: I made a separate ticket regarding my previous comment (including function_assignment.patch): Ticket #2319 - function::operator= should "move", copy assignment should have by-value argument
This patch should give a significant performance improvement for swap(). Note, however, that it does not provide the nothrow guarantee for swap() due to the small-object optimization: small objects are still copied by move_assign, so their constructors could throw. We may or may not want a no-throw swap for Boost.Function, regardless of what std::(tr1::)function say; that we can handle on-list.
I'm applying a variant of this patch to trunk now, and will close the ticket then.