|
Boost : |
Subject: [boost] [function] new implementation
From: Domagoj Saric (dsaritz_at_[hidden])
Date: 2010-10-30 08:53:42
Hi,
<...those that followed the latest function related discussions can skip the
first paragraph...>
For about a year and half now (or even more but that's about how long I've
been involved in the matter) there have been repeated
discussions/requests/complaints about various aspects of Boost.Function.
Most of those fall into one or more of these categories:
- overall code speed overhead
- overall code bloat overhead
- lack of configurability
- dependencies on RTTI and exception handling
with the most frequently brought up specific issue being the various
implications (in all of the above mentioned areas) of the hardcoded
throw-on-empty behaviour...
Unfortunately Boost.Function code remained virtually unchanged (except for
the added RTTI emulation for platforms w/o RTTI support) with all of the
issues still remaining...
The new implementation proposal @:
https://svn.boost.org/svn/boost/sandbox/function/boost/function
http://www.boostpro.com/vault/index.php?&direction=0&order=&directory=Function
Objects
solves or at least provides drastic improvements for all of the mentioned
issues.
It is intended as a drop in replacement for the current implementation and
as such I have so far tested it in the following ways:
- compiling some real world code: VC10, GCC 4.2.1, LLVM-GCC 4.2.1, GCC
4.5.1, GCC 4.6, Clang 2.0, Clang 2.8
- compiling and running a real world project: VC10, Clang 2.8
- building Boost trunk: VC10, GCC 4.2.1, GCC 4.5.1, Clang 2.0
- running Boost regression tests for Function, Signals, Signals2, Thread,
Program Options and Graph libraries: VC10, GCC 4.2.1, Clang 2.0.
(All of the tests passed except for two bogus assertion failure boxes that
pop up with VC10 and the Program Options tests during the automated
regression test procedure that OTOH do not popup when I run the same tests
manually through the VC10 IDE, i.e. the tests finish successfully...I tried
to make sure all the relevant compiler switches were the same in both cases
so I am out of ideas what else can make the difference.....)
Next, let me give you a comparison of for-size-optimized code generated by
VC10 with both the current and the proposed implementation for a short code
snippet demonstrating invocation, construction and assignment:
struct X
{
int foo( int const x ) const { return x + y; };
int y;
};
int main( int /*argc*/, char * * /*argv*/ )
{
using namespace boost;
X x;
function<int(int)> foof( boost::bind( &X::foo, &x, _1 ) );
foof( 0 );
function<int(int)> barf;
barf = foof;
barf( 1 );
return 0;
}
Notice in new_function_code_listing.txt how all of the wrapper code in the
new Function was completely transparent to the compiler and was completely
inlined to relatively simple machine code with direct calls to/through the
vtable pointers...while OTOH in current_function_code_listing.txt the code
generated for the "barf = foof;" assignement alone is several times larger
than the entire code generated for the new Function implementation.
The small asm snippet of the generated invoker/'thunk'/'trampoline' code
also demonstrates the effects of various tweaks done to minimize or all
together remove the need for any stack adjustment on the invoker side...
The new implementation of course has the same semantics and exception safety
guarantee levels as the original/current one.
Listing all of the improvements and detailed implementation changes here in
the first post would be quite cluttering as they are quite numerous. Those
that desire more tests and details right now, can follow this trail:
http://lists.boost.org/Archives/boost/2010/01/160908.php
As for the dependencies issue this is solved in several ways:
- use of exception handling: new code uses guard objects instead of
explicit exception handling and provides a way to change on-empty-behaviour
so that the EH code/dependency can easily be removed/disabled
- use of RTTI (for getting the type of the stored function object): this
can be globally disabled with the BOOST_FUNCTION_NO_RTTI macro.
BOOST_NO_RTTI is not used for this purpose for backward compatibility
reasons: in the current implementation it actually turns on RTTI
emulation...The idea is to also add per instantiation RTTI on-off
configurability...
'Configurability' will probably be the most controversial issue/change
because it required the addition of an extra template parameter through
which an MPL list of policies can be specified at compile-time. This
parameter is properly defaulted to allow existing code to compile without
changes while providing current Boost.Function behaviour (e.g. on-empty-
behaviour). As all the performed tests showed this to also be true in
practice the proposed code should IMO go in as a straight drop-in
replacement but, of course, I might have missed some important higher level
detail/concern so this is open to discussion...
Currently two policies are supported:
- on throw behaviour (with throw-on-empty, nop-on-empty and assert-on-empty
handlers provided)
- whether the function instantiation should mark itself (i.e. its
operator()) as nothrow.
Additional policies my be added later, for example:
- sizeof and/or alignment of the SBO buffer
- default allocator
- per instantiation RTTI on/off
...
Finally, there is one more addition, somewhat 'unholy' as it brings in the
concept of binding into Boost.Function but considering its efficiency and/or
verbosity benefits (and the fact that the first issue can be 'explained
away' by saying that this is not actually 'binding' but a workaround for the
lack-of-delegates-support C++ language deficiency).
The addition is in the form of two more assign() member function overloads
(one for a free function that has the exact signature as the Boost.Function
instantiation, and one for a member function that has a matching signature
with the addition of a this pointer) that allow assignment of (suitably
typed) plain (free or member) functions specified as non-type-template
parameters. This allows for the removal of the extra
indirect-call-through-function-pointer otherwise incurred by traditional
assignment of function pointers (not functors), which is actually a rather
common use case. For example, in the above example given, one can now write:
foof.assign<X, &X::foo>( x );
and get a trivial invoker with the X::foo() target function inlined straight
in...
-- "What Huxley teaches is that in the age of advanced technology, spiritual devastation is more likely to come from an enemy with a smiling face than from one whose countenance exudes suspicion and hate." Neil Postman
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk