#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.