[sigc] Speed comparison: Boost.Signals vs. libsigc++2
- From: Jonathan Brandmeyer <jbrandmeyer earthlink net>
- To: libsigc-list gnome org
- Subject: [sigc] Speed comparison: Boost.Signals vs. libsigc++2
- Date: Thu, 02 Dec 2004 20:10:21 -0500
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]