[sigc] Speed comparison: Boost.Signals vs. libsigc++2



Here is a small bench test derived from a test that someone posted to
the Boost development mailing list.  I have altered it to compare
libsigc++ vs. Boost.Signals vs. a trivial implementation (the original
only compared the simple version vs. Boost.Signals).

Compiled and linked with:
g++ -O3 -pthread -I. `pkg-config --cflags sigc++-2.0` \
signals_speed_test.cpp `pkg-config --libs sigc++-2.0` \
-lboost_signals-gcc -lboost_date_time-gcc

Timings were generated with GCC 3.3.5 (Debian) on an 800 MHz PIII, with
Boost.Signals 1.31 and libsigc++ 2.0.6.  I believe that the sharp change
in timings from the 1000 slot tests to the 5000 slot tests indicates
where the program exceeded the CPU cache size.

-Jonathan


===== 100000 Total Calls =====
Num Slots    Calls/Slot    Boost     Lite      SigC
---------    ----------    -------   -------   -------
        1        100000     0.5028    0.0216    0.0203
       10         10000     0.1901    0.0074    0.0071
       50          2000     0.1562    0.0067    0.0060
      100          1000     0.1540    0.0062    0.0056
      250           400     0.1535    0.0072    0.0064
      500           200     0.1509    0.0086    0.0089
     1000           100     0.1660    0.0078    0.0080
     5000            20     0.2060    0.0332    0.0393
    10000            10     0.2083    0.0353    0.0369
    50000             2     0.2076    0.0328    0.0392
   100000             1     0.2098    0.0331    0.0408

===== 1000000 Total Calls =====
Num Slots    Calls/Slot    Boost     Lite      SigC
---------    ----------    -------   -------   -------
        1       1000000     5.0554    0.2144    0.2091
       10        100000     1.8431    0.0776    0.0723
       50         20000     1.5655    0.0672    0.0610
      100         10000     1.5379    0.0718    0.0566
      250          4000     1.5257    0.0811    0.0645
      500          2000     1.5377    0.0785    0.0661
     1000          1000     1.6198    0.0836    0.0763
     5000           200     2.0899    0.3669    0.4213
    10000           100     2.0668    0.3799    0.4344
    50000            20     2.0324    0.3784    0.4284
   100000            10     2.0828    0.3839    0.4258
   500000             2     2.3263    0.3247    0.4102


Since the "Lite" implementation did not compile with GCC 3.4.3, I have
also attached a version that does not include it.  These timings follow,
and are somewhat better than the ones for GCC 3.3.5.  The test file for
this test is signals_speed_test2.cpp.

===== 100000 Total Calls =====
Num Slots    Calls/Slot    Boost     SigC
---------    ----------    -------   -------
        1        100000     0.4202    0.0212
       10         10000     0.1645    0.0067
       50          2000     0.1402    0.0050
      100          1000     0.1452    0.0051
      250           400     0.1786    0.0058
      500           200     0.1435    0.0063
     1000           100     0.1550    0.0061
     5000            20     0.2052    0.0316
    10000            10     0.2078    0.0324
    50000             2     0.2092    0.0317
   100000             1     0.2056    0.0305

===== 1000000 Total Calls =====
Num Slots    Calls/Slot    Boost     SigC
---------    ----------    -------   -------
        1       1000000     4.1999    0.2109
       10        100000     1.6485    0.0659
       50         20000     1.4226    0.0543
      100         10000     1.4051    0.0506
      250          4000     1.3954    0.0577
      500          2000     1.4166    0.0611
     1000          1000     1.5768    0.0759
     5000           200     2.0886    0.3302
    10000           100     2.0790    0.3297
    50000            20     2.0653    0.3200
   100000            10     2.0638    0.3615
   500000             2     2.0517    0.3068
#ifndef lite__signals__Connection__hpp_
#define lite__signals__Connection__hpp_

#include "boost/any.hpp"

namespace lite
{

namespace signals
{

class Connection
{
public:
  Connection()
    : disconnect_(0)
    , replace_(0)
    , token_()
  {
  }

  Connection(
      void (*disconnect)(
          boost::any const & token),
      void (*replace)(
          boost::any const & token,
          boost::any const & function),
      boost::any const & token)
    : disconnect_(disconnect)
    , replace_(replace)
    , token_(token)
  {
  }

  void disconnect()
  {
    if (disconnect_)
    {
      disconnect_(token_);
      disconnect_ = 0;
      replace_ = 0;
      token_ = boost::any();
    }
  }

  void replace_any(
      boost::any const & function)
  {
    if (replace_)
    {
      replace_(token_, function);
    }
  }

  boost::any const & token() const
  {
    return token_;
  }

protected:
  void (*disconnect_)(boost::any const & token);
  void (*replace_)(boost::any const & token, boost::any const & func);
  boost::any token_;
};


template <typename SIGNAL>
class Typed_Connection
  : public Connection
{
public:
  Typed_Connection()
    : Connection()
  {
  }

  Typed_Connection(
      boost::any const & token)
    : Connection(
        &SIGNAL::disconnect_any_token,
        &SIGNAL::replace_any_token,
        token)
  {
  }

  void replace(
      typename SIGNAL::Function_Type const & func)
  {
    replace_any(func);
  }
};


} // end signals namespace

} // end lite namespace

#endif // lite__signals__Connection__hpp_
#ifndef lite__Signal__hpp_
#define lite__Signal__hpp_

#include "Connection.hpp"
#include "boost/any.hpp"
#include "boost/function.hpp"
#include <list>

namespace lite
{

namespace signals
{

namespace detail
{

typedef unsigned long Signal_Info_Id;

template <typename Signature>
struct Signal_Info
{
  typedef boost::function<Signature> Function_Type;
  Signal_Info_Id id_;
  Function_Type function_;

  Signal_Info(
      Signal_Info_Id id,
      Function_Type const & function)
    : id_(id)
    , function_(function)
  {
  }
};


template <typename Signature>
class Signal_Base
{
public:
  typedef Signal_Base self_type;

private:
  struct Connection_Token
  {
    Signal_Base * signal_;
    Signal_Info_Id id_;
    Connection_Token(
        Signal_Base * signal,
        Signal_Info_Id id)
      : signal_(signal)
      , id_(id)
    {
    }
  };

public:
  typedef boost::function< Signature > Function_Type;

  typedef Typed_Connection<self_type> Connection;
  friend class Connection;
  typedef Connection connection_type;

private:
  typedef Signal_Info< Signature > Info;

public:
  Signal_Base()
    : id_pool_(0)
    , list_()
  {
  }

  Connection connect(
      Function_Type const & f)
  {
    Info info(id_pool_++, f);
    list_.push_back(info);
    return Connection(Connection_Token(this, info.id_));
  }

  void disconnect(
      signals::Connection const & conn)
  {
    disconnect_(boost::any_cast<Connection_Token>(&conn.token()));
  }

  void replace(
      signals::Connection const & conn,
      Function_Type const & func)
  {
    replace_(boost::any_cast<Connection_Token>(&conn.token()), func);
  }

  void disconnect_all()
  {
    list_.clear();
  }

  void disconnect_all_slots()
  {
    disconnect_all();
  }

private:
  Signal_Info_Id id_pool_;
protected:
  // A vector is quite a bit faster, but the disconnect/connect code
  // becomes a bit more complex, especially when allowing them
  // inside a signal handler...
  // In my testing, I see measurable performance difference
  // when we have more than 1000 slots.  Since our typical use cases
  // do not get near than number, I'll leave it like this for now.
  typedef std::list< Info > list;
  mutable list list_;
private:

  void replace_(
      Connection_Token const * token,
      Function_Type const & function)
  {
    if (token && token->signal_ == this)
    {
      typename list::iterator i = list_.begin();
      typename list::iterator const end = list_.end();
      for (; i != end; ++i)
      {
        if (i->id_ == token->id_)
        {
          i->function_ = function;
          break;
        }
      }
    }
  }

  void disconnect_(
      Connection_Token const * token)
  {
    replace_(token, Function_Type());
  }

  static void disconnect_any_token(
      boost::any const & any_token)
  {
    Connection_Token const * token(
        boost::any_cast<Connection_Token>(&any_token));
    if (token && token->signal_)
    {
      token->signal_->disconnect_(token);
    }
  }

  static void replace_any_token(
      boost::any const & any_token,
      boost::any const & any_func)
  {
    Connection_Token const * token(
        boost::any_cast<Connection_Token>(&any_token));
    if (token && token->signal_)
    {
      Function_Type function(boost::any_cast<Function_Type>(any_func));
      token->signal_->replace_(token, function);
    }
  }
};

} // end detail namespace


template <typename Signature>
struct Signal;

template <typename R>
struct Signal<R (void)>
  : public detail::Signal_Base< R (void)>
{
  R operator()(void) const
  {
    typename list::iterator i = list_.begin();
    while (i != list_.end())
    {
      if (i->function_)
      {
        (i++)->function_();
      }
      else
      {
        i = list_.erase(i);
      }
    }
  }
};

template <typename R, typename T1>
struct Signal<R (T1)>
  : public detail::Signal_Base< R (T1)>
{
  R operator()(T1 t1) const
  {
    typename list::iterator i = list_.begin();
    while (i != list_.end())
    {
      if (i->function_)
      {
        (i++)->function_(t1);
      }
      else
      {
        i = list_.erase(i);
      }
    }
  }
};

template <typename R, typename T1, typename T2>
struct Signal<R (T1, T2)>
  : public detail::Signal_Base< R (T1, T2)>
{
  R operator()(T1 t1, T2 t2) const
  {
    typename list::iterator i = list_.begin();
    while (i != list_.end())
    {
      if (i->function_)
      {
        (i++)->function_(t1, t2);
      }
      else
      {
        i = list_.erase(i);
      }
    }
  }
};

template <typename R, typename T1, typename T2, typename T3>
struct Signal<R (T1, T2, T3)>
  : public detail::Signal_Base< R (T1, T2, T3)>
{
  R operator()(T1 t1, T2 t2, T3 t3) const
  {
    typename list::iterator i = list_.begin();
    while (i != list_.end())
    {
      if (i->function_)
      {
        (i++)->function_(t1, t2, t3);
      }
      else
      {
        i = list_.erase(i);
      }
    }
  }
};

template <typename R, typename T1, typename T2, typename T3,
    typename T4>
struct Signal<R (T1, T2, T3, T4)>
  : public detail::Signal_Base< R (T1, T2, T3, T4)>
{
  R operator()(T1 t1, T2 t2, T3 t3, T4 t4) const
  {
    typename list::iterator i = list_.begin();
    while (i != list_.end())
    {
      if (i->function_)
      {
        (i++)->function_(t1, t2, t3, t4);
      }
      else
      {
        i = list_.erase(i);
      }
    }
  }
};

template <typename R, typename T1, typename T2, typename T3,
    typename T4, typename T5>
struct Signal<R (T1, T2, T3, T4, T5)>
  : public detail::Signal_Base< R (T1, T2, T3, T4, T5)>
{
  R operator()(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5) const
  {
    typename list::iterator i = list_.begin();
    while (i != list_.end())
    {
      if (i->function_)
      {
        (i++)->function_(t1, t2, t3, t4, t5);
      }
      else
      {
        i = list_.erase(i);
      }
    }
  }
};

template <typename R, typename T1, typename T2, typename T3,
    typename T4, typename T5, typename T6>
struct Signal<R (T1, T2, T3, T4, T5, T6)>
  : public detail::Signal_Base< R (T1, T2, T3, T4, T5, T6)>
{
  R operator()(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6) const
  {
    typename list::iterator i = list_.begin();
    while (i != list_.end())
    {
      if (i->function_)
      {
        (i++)->function_(t1, t2, t3, t4, t5, t6);
      }
      else
      {
        i = list_.erase(i);
      }
    }
  }
};

template <typename R, typename T1, typename T2, typename T3,
    typename T4, typename T5, typename T6, typename T7>
struct Signal<R (T1, T2, T3, T4, T5, T6, T7)>
  : public detail::Signal_Base< R (T1, T2, T3, T4, T5, T6, T7)>
{
  R operator()(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7) const
  {
    typename list::iterator i = list_.begin();
    while (i != list_.end())
    {
      if (i->function_)
      {
        (i++)->function_(t1, t2, t3, t4, t5, t6, t7);
      }
      else
      {
        i = list_.erase(i);
      }
    }
  }
};



} // end signals namespace

} // end lite namespace

#endif // lite__Signal__hpp_
/*
History:
Originally posted on 2004-11-30 to boost lists boost org by Jody Hagins.
Modified on 2004-12-01 to use the Boost.Date Time library by Jody Hagins.
Modified on 2004-12-02 to additionally test libsigc++2 by Jonathan Brandmeyer.
*/

#include "Connection.hpp"
#include "Signal.hpp"
#include "boost/signal.hpp"
#include "boost/bind.hpp"
#include "boost/date_time/posix_time/posix_time_types.hpp"

#include <sigc++/sigc++.h>
#include <vector>
#include <cstdio>
#include <iostream>


struct X
{
  X(int x = 0) : x_(x) { }
  int x_;
};


void foo(X & x)
{
  x.x_ += 1234;
}

void blarg(X & x)
{
  x.x_ /= 71;
}

struct bar
{
  void operator()(X & x) { x.x_ *= 7; }
};

struct foobar
{
  void doit(X & x) { x.x_ += 7; }
};

struct timing
{
  size_t nslots_;
  size_t ncalls_;
  double elapsed1_;
  double elapsed2_;
  double elapsed3_;
};

inline
double
as_double(
    boost::posix_time::time_duration const & duration)
{
  double result = duration.ticks();
  return result /= duration.ticks_per_second();
}

int
main(
    int argc,
    char * argv[])
{
  try
  {
    std::vector<timing> timings;
    size_t num_slots[] =
    {
      1, 10, 50, 100, 250, 500, 1000, 5000, 10000, 50000, 100000,
      500000, 0
    };
    typedef lite::signals::Signal<void (X &)> Signal1;
    Signal1 signal1;
    typedef boost::signal<void (X &)> Signal2;
    Signal2 signal2;
    typedef sigc::signal<void, X&> Signal3;
    Signal3 signal3;

    size_t totcalls[] = { 1000, 10000, 100000, 1000000, 0 };
    for (size_t * tc = totcalls; *tc > 0; ++tc)
    {
      size_t total_calls = *tc;
      for (size_t * ns = num_slots; *ns > 0 && *ns <= total_calls; ++ns)
      {
        size_t nslots = *ns;
        size_t niters = total_calls / nslots;

        signal1.disconnect_all_slots();
        signal1.connect(bar());
        foobar foobar1;
        signal1.connect(boost::bind(&foobar::doit, &foobar1, _1));

        signal2.disconnect_all_slots();
        signal2.connect(bar());
        foobar foobar2;
        signal2.connect(boost::bind(&foobar::doit, &foobar2, _1));

        signal3.clear();
        signal3.connect( bar());
        foobar foobar3;
        signal3.connect( sigc::mem_fun( foobar3, &foobar::doit));

        std::vector<foobar> slots(nslots);
        for (size_t i = 0; i < slots.size(); ++i)
        {
          signal1.connect(boost::bind(&foobar::doit, &slots[i], _1));
          signal2.connect(boost::bind(&foobar::doit, &slots[i], _1));
          signal3.connect(sigc::mem_fun( slots[i], &foobar::doit));
        }

        X my_x(5);
        timing t;
        t.nslots_ = nslots;
        t.ncalls_ = niters;
        boost::posix_time::ptime start_time(
            boost::posix_time::microsec_clock::local_time());
        for (size_t i = 0; i < niters; ++i)
        {
          signal1(my_x);
        }
        boost::posix_time::ptime stop_time(
            boost::posix_time::microsec_clock::local_time());
        t.elapsed1_ = as_double(stop_time - start_time);

        X bs_x(5);
        start_time = boost::posix_time::microsec_clock::local_time();
        for (size_t i = 0; i < niters; ++i)
        {
          signal2(bs_x);
        }
        stop_time = boost::posix_time::microsec_clock::local_time();
        t.elapsed2_ = as_double(stop_time - start_time);

        X sc_x(5);
        start_time = boost::posix_time::microsec_clock::local_time();
        for (size_t i = 0; i < niters; ++i)
        {
          signal3(sc_x);
        }
        stop_time = boost::posix_time::microsec_clock::local_time();
        t.elapsed3_ = as_double(stop_time - start_time);

        timings.push_back(t);

        if (my_x.x_ != bs_x.x_ || my_x.x_ != sc_x.x_)
        {
          std::cerr << "my_x(" << my_x.x_ << ") != bs_x("
              << bs_x.x_ << ")\n";
        }
      }
    }
    size_t last_size = (size_t)-1;
    for (size_t i = 0; i < timings.size(); ++i)
    {
      if (last_size != timings[i].nslots_ * timings[i].ncalls_)
      {
        last_size = timings[i].nslots_ * timings[i].ncalls_;
        fprintf(stdout, "\n===== %u Total Calls =====\n", last_size);
        fprintf(stdout, "Num Slots    Calls/Slot    Boost     Lite      SigC\n");
        fprintf(stdout, "---------    ----------    -------   -------   -------\n");
      }
      fprintf(stdout, "%9u%14u%11.4f%10.4f%10.4f\n",
          timings[i].nslots_,
          timings[i].ncalls_,
          timings[i].elapsed2_,
          timings[i].elapsed1_,
          timings[i].elapsed3_);
    }
    return 0;
  }
  catch (std::exception const & ex)
  {
    std::cerr << "exception: " << ex.what() << std::endl;
  }
  return 1;
}  


#include "boost/signal.hpp"
#include "boost/bind.hpp"
#include "boost/date_time/posix_time/posix_time_types.hpp"

#include <sigc++/sigc++.h>
#include <vector>
#include <cstdio>
#include <iostream>


struct X
{
  X(int x = 0) : x_(x) { }
  int x_;
};


void foo(X & x)
{
  x.x_ += 1234;
}

void blarg(X & x)
{
  x.x_ /= 71;
}

struct bar
{
  void operator()(X & x) { x.x_ *= 7; }
};

struct foobar
{
  void doit(X & x) { x.x_ += 7; }
};

struct timing
{
  size_t nslots_;
  size_t ncalls_;
  double elapsed2_;
  double elapsed3_;
};

inline
double
as_double(
    boost::posix_time::time_duration const & duration)
{
  double result = duration.ticks();
  return result /= duration.ticks_per_second();
}

int
main(
    int argc,
    char * argv[])
{
  try
  {
    std::vector<timing> timings;
    size_t num_slots[] =
    {
      1, 10, 50, 100, 250, 500, 1000, 5000, 10000, 50000, 100000,
      500000, 0
    };

    typedef boost::signal<void (X &)> Signal2;
    Signal2 signal2;
    typedef sigc::signal<void, X&> Signal3;
    Signal3 signal3;

    size_t totcalls[] = { 1000, 10000, 100000, 1000000, 0 };
    for (size_t * tc = totcalls; *tc > 0; ++tc)
    {
      size_t total_calls = *tc;
      for (size_t * ns = num_slots; *ns > 0 && *ns <= total_calls; ++ns)
      {
        size_t nslots = *ns;
        size_t niters = total_calls / nslots;

        signal2.disconnect_all_slots();
        signal2.connect(bar());
        foobar foobar2;
        signal2.connect(boost::bind(&foobar::doit, &foobar2, _1));

        signal3.clear();
        signal3.connect( bar());
        foobar foobar3;
        signal3.connect( sigc::mem_fun( foobar3, &foobar::doit));

        std::vector<foobar> slots(nslots);
        for (size_t i = 0; i < slots.size(); ++i)
        {
          signal2.connect(boost::bind(&foobar::doit, &slots[i], _1));
          signal3.connect(sigc::mem_fun( slots[i], &foobar::doit));
        }

        timing t;
        t.nslots_ = nslots;
        t.ncalls_ = niters;
        X bs_x(5);
        boost::posix_time::ptime start_time = boost::posix_time::microsec_clock::local_time();
        for (size_t i = 0; i < niters; ++i)
        {
          signal2(bs_x);
        }
        boost::posix_time::ptime stop_time = boost::posix_time::microsec_clock::local_time();
        t.elapsed2_ = as_double(stop_time - start_time);

        X sc_x(5);
        start_time = boost::posix_time::microsec_clock::local_time();
        for (size_t i = 0; i < niters; ++i)
        {
          signal3(sc_x);
        }
        stop_time = boost::posix_time::microsec_clock::local_time();
        t.elapsed3_ = as_double(stop_time - start_time);

        timings.push_back(t);

        if (bs_x.x_ != sc_x.x_)
        {
          std::cerr << "bs_x(" << bs_x.x_ << ") != sc_x("
              << sc_x.x_ << ")\n";
        }
      }
    }
    size_t last_size = (size_t)-1;
    for (size_t i = 0; i < timings.size(); ++i)
    {
      if (last_size != timings[i].nslots_ * timings[i].ncalls_)
      {
        last_size = timings[i].nslots_ * timings[i].ncalls_;
        fprintf(stdout, "\n===== %u Total Calls =====\n", last_size);
        fprintf(stdout, "Num Slots    Calls/Slot    Boost     SigC\n");
        fprintf(stdout, "---------    ----------    -------   -------\n");
      }
      fprintf(stdout, "%9u%14u%11.4f%10.4f\n",
          timings[i].nslots_,
          timings[i].ncalls_,
          timings[i].elapsed2_,
          timings[i].elapsed3_);
    }
    return 0;
  }
  catch (std::exception const & ex)
  {
    std::cerr << "exception: " << ex.what() << std::endl;
  }
  return 1;
}  




[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]