Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is boost::function slow?

I was doing some timing tests and one of my tests was to compare different ways of calling functions. I called N functions using various means. I tried regular function calls, virtual function calls, function pointers, and boost::function.

I did this in Linux using gcc and -O3 optimization.

As expected virtual calls are slower than regular function calls. However the surprising thing is that boost::function clocked in at 33% slower than the virtual calls.

Has anyone else noticed this? Any clue to why this is?

like image 529
Nathan Doromal Avatar asked Feb 18 '23 06:02

Nathan Doromal


2 Answers

Regular functions can be inlined by the compiler if possible, but boost::function can never be inlined. That is one big difference.

The second difference is, boost::function implements type-erasure which means it uses indirection to invoke the actual function. Means it first calls a virtual function which then invokes your function. So typically it involves (minimum) two function calls (one of them is virtual). That is huge difference.

So based on this analysis, one could infer this (without even writing test code):

slowest ------------------------------------------------------> fastest 
        boost::function < virtual function < regular function 
slowest ------------------------------------------------------> fastest

which is indeed the case, in your test code.

Note that it is true for std::function also (which is available since C++11).

like image 175
Nawaz Avatar answered Feb 24 '23 04:02

Nawaz


A boost::function can hold not only a function pointer, but an entire copy of an arbitrary object which it calls a possibly virtual operator() on.

It can help to understand how it might work (for exposition).

Here is a toy implementation of a boost::function type trick:

struct helper_base { virtual void do_it() = 0; };
template<typename Func>
struct helper:helper_base {
  Func func;
  helper(Func f):func(f) {}
  virtual void do_it() override { func(); }
};
struct do_something_later {
  boost::unique_ptr<helper_base> pImpl;
  template<typename Func>
  do_something_later( Func f ):pImpl(make_shared<helper<Func>>(f))
  {}
  void operator()() { (*pImpl).do_it(); }
private:
  do_something_later( do_something_later const& ); // deleted
  void operator=( do_something_later const& ); // deleted
};

Here my do_something_later takes an arbitrary object (Func) and calls operator() on it on demand. It wraps the type of the thing we are calling operator() on in a type erasure helper, and then invokes the operator() via a virtual function.

Func type could be a function pointer, or it could be a functor with state. Anything copyable with an operator() is fair game. As far as the user of do_something_later is concerned, there is only one binary interface.

boost::function (and std::function) uses basically this same technique (with lots of improvements) to turn a whole set of possible interfaces into one interface. The cost involves calling a virtual function (or the equivalent level of indirection).

like image 39
Yakk - Adam Nevraumont Avatar answered Feb 24 '23 03:02

Yakk - Adam Nevraumont