Boost logo

Boost :

Subject: Re: [boost] [type_erasure] Review started (July 18-27, 2012)
From: Steven Watanabe (watanabesj_at_[hidden])
Date: 2012-08-04 13:15:08


AMDG

On 08/03/2012 06:36 PM, Chapman, Alec wrote:
> On 08/02/2012 8:23 AM, Steven Watanabe wrote:
>> Use the any(any<Concept2, T2>, binding<Concept>) constructor.
>> It allows you to convert the vtable once, and re-use it for all the anys.
>
> This constructor doesn't seem to work with gcc 4.7.0 because the copy constructor for binding doesn't exist:
>
> typedef mpl::vector<te::copy_constructible<>, te::addable<>, te::incrementable<>> add_inc;
> typedef mpl::vector<te::copy_constructible<>, te::incrementable<>> inc;
> te::any<add_inc> x(3);
> te::binding<inc> binding(te::binding_of(x), mpl::map<mpl::pair<te::_self, te::_self>>());
> te::any<inc> y(x, binding);
>
> boost/type_erasure/any.hpp:444:9: error: use of deleted function âboost::type_erasure::binding<boost::mpl::vector<boost::type_erasure::copy_constructible<>, boost::type_erasure::incrementable<> > >::binding(const boost::type_erasure::binding<boost::mpl::vector<boost::type_erasure::copy_constructible<>, boost::type_erasure::incrementable<> > >&)â
>

I think this is a bug in boost::shared_ptr.

>
> So I switched to VC10 for my benchmarks.
> This time I ran five tests summing a vector of anys by:
> 1) using the any elements directly.
> 2) up-converting using the default constructor (and capturing by reference)
> 3) up-converting using a single binding object
> 4) up-converting, looking up the binding in a map
> 5) up-converting, looking up the binding in a map that directly compares addresses of type_info objects
>
> The results were:
> 1) 0.73 s (no conversion)
> 2) 41.19 s (default conversion)
> 3) 3.80 s (single binding)
> 4) 31.52 s (slow, correct lookup)
> 5) 4.92 s (fast lookup)
>
> Using std::type_info::before for map comparisons adds a lot of overhead (4 vs 5). In practice I could get away with just comparing addresses and storing multiple copies.
>
> For me the relevant comparison is test 5 vs test 1, so conversion is still 7x slower than not converting (shared_ptr copying?). Recall from my previous test that using static vtables for conversions only added 30% overhead.
>
> My impression is that static vtables would be a better default behavior, possibly with the option for the user to specify a dynamic binding if they really need to optimize for many repeated function calls. The current behavior has huge overhead by default. Avoiding this requires the user to write a decent amount of code that is dangerous in terms of introducing undefined behavior (if the wrong binding is applied), or creating its own performance problems (the choice of map lookup), and even then the overhead still appears to be very large. Specifying a binding is essentially like casting from a void*. I like that this capability is provided, but I think the library should strive to keep the number of cases where it is required in user code to a minimum.
>

- Wrapping the any increases the cost of all any
  operations. I'm not convinced that a 1-to-1
  ratio of calls/conversions is realistic usage.
- This particular example can (and will)
  be optimized to re-use the original vtable.

>>> I propose an overloaded constructor that signals that the underlying
>>> object
>> should be held by reference but copied when the any object is copied:
>>>
>>> int i = 0;
>>> any<requirements> a(i, capture_reference); any<requirements> b(a);
>>> ++a; // i == 1
>>> ++b; // i == 1 still, any_cast<int>(b) == 2
>>>
>>> This has worked well in my own code and should be easy to implement.
>>> I think
>> this would also avoid the bug mentioned at the bottom of the
>> references page of the documentation.
>>>
>>
>> I don't think this is a good idea, as it would require an extra flag
>> whether it's needed or not.
>
> Steven, do you have any thoughts on my previous reply to this? I have implemented this without any cost.
>

It can't be implemented without cost. There
are really two options:
1) Add a flag
2) Manage the vtable /inside/ the dispatching layer.
  - Implies that every function knows about
    all the other functions. This extra coupling
    makes the more advanced features of the library
    hard and also increases code bloat.

I'm assuming that you had already taken option (2),
so it didn't impose any additional cost.

Also,
- It can't handle const references correctly
- It doesn't work with type erased function
  functions that need to return a reference

I think such a feature is too limited for the
costs it imposes.

> I still feel that the features I've commented on are at too much of an experimental stage and that better solutions exist. My experience has been that erased types need to behave as similarly as possible to their underlying types, especially if they are to be used in generic code. I don't see the advantages of forcing the user to deal with what is essentially a parallel version of the type system for references when there is IMHO a more natural approach that carries no cost and is more generally useable. See my comments above as well. Conversions become less useful if they can't be used naturally like other types.
>

In Christ,
Steven Watanabe


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk