I wrote some benchmarks for Boost and GSL where I initialize a vector
of random numbers and then raise it to some random exponent, and I
redo the operations a certain number of times and report the average
time.
std::transform and std::for_each take the same time, GSL's filling in
manually and Boosts's filling in manually also take the same time BUT,
manual fil is a bit faster than using std::transform and
std::for_each. Any ideas why?
Runtimes:
Boost vector initialization:
0.000000000000000
GSL vector initialization:
0.000000000000000
Boost transform avg time:
0.002141000000000
Boost for_each avg time:
0.002094000000000
Boost manual fill avg time:
0.001811000000000
GSL avg time:
0.001820000000000
The code is below:
/*
* main.cpp
*
* Created on: 2010-07-08
* Author: max
*/
#include <iostream>
#include <vector>
#include <algorithm>
#include
#include <fstream>
#include <ctime> // std::time
#include
#include
#include
#include // time the runs
// For random number generation
#include
#include
#include
#include
// Sun CC doesn't handle boost::iterator_adaptor yet
#if !defined(__SUNPRO_CC) || (__SUNPRO_CC > 0x530)
#include
#endif
#ifdef BOOST_NO_STDC_NAMESPACE
namespace std {
using ::time;
}
#endif
// This is a typedef for a random number generator.
// Try boost::mt19937 or boost::ecuyer1988 instead of boost::minstd_rand
typedef boost::minstd_rand base_generator_type;
// GSL includes
#include
#include
void my_pow(double & val, double exponent) {
val = std::pow(val, exponent);
}
int main() {
// Size of assigned vector
unsigned N = 10000;
// number of times to repeat the simulation
// unsigned numrepeat = 10000;
// exponent to use
double exponent; // = 12.12345678901234;
// value to exponentiate
double fill_val; // = 1.12345678901234;
// Define a random number generator and initialize it with a reproducible
// seed.
// (The seed is unsigned, otherwise the wrong overload may be selected
// when using mt19937 as the base_generator_type.)
base_generator_type generator(42u);
// Define a uniform random number distribution which produces "double"
// values between 0 and 1 (0 inclusive, 1 exclusive).
boost::uniform_real<> uni_dist(0, 1);
boost::variate_generator > uni(
generator, uni_dist);
/*
* Change seed to something else.
*
* Caveat: std::time(0) is not a very good truly-random seed. When
* called in rapid succession, it could return the same values, and
* thus the same random number sequences could ensue. If not the same
* values are returned, the values differ only slightly in the
* lowest bits. A linear congruential generator with a small factor
* wrapped in a uniform_smallint (see experiment) will produce the same
* values for the first few iterations. This is because uniform_smallint
* takes only the highest bits of the generator, and the generator itself
* needs a few iterations to spread the initial entropy from the lowest bits
* to the whole state.
*/
// Set the generator later for each piece of code
//generator.seed(static_cast<unsigned int> (std::time(0)));
// Vector of times
boost::numeric::ublas::vector<double> times(N);
// Boost timer
boost::timer t;
std::cout.precision(15);
t.restart();
boost::numeric::ublas::vector<double> boost_v(N);
std::cout << "Boost vector initialization: " << std::endl;
std::cout << std::fixed << t.elapsed() << std::endl;
t.restart();
gsl_vector * gsl_v = gsl_vector_alloc(N);
std::cout << "GSL vector initialization: " << std::endl;
std::cout << std::fixed << t.elapsed() << std::endl;
// Filling Boost using std::transform
generator.seed(static_cast<unsigned int> (std::time(0)));
for (unsigned i = 0; i < N; ++i) {
fill_val = uni();
exponent = 2.0 + uni();
t.restart();
std::fill(boost_v.begin(), boost_v.end(), fill_val);
std::transform(boost_v.begin(), boost_v.end(), boost_v.begin(),
boost::bind(pow, _1, exponent));
times(i) = t.elapsed();
}
std::cout << "Boost transform avg time: " << std::endl;
std::cout << std::fixed << boost::numeric::ublas::sum(times) / (double) N
<< std::endl;
// Filling Boost using std::for_each
generator.seed(static_cast<unsigned int> (std::time(0)));
for (unsigned i = 0; i < N; ++i) {
fill_val = uni();
exponent = 2.0 + uni();
t.restart();
std::fill(boost_v.begin(), boost_v.end(), fill_val);
std::for_each(boost_v.begin(), boost_v.end(), boost::bind(my_pow, _1,
exponent));
times(i) = t.elapsed();
}
std::cout << "Boost for_each avg time: " << std::endl;
std::cout << std::fixed << boost::numeric::ublas::sum(times) / (double) N
<< std::endl;
// Filling Boost manually
generator.seed(static_cast<unsigned int> (std::time(0)));
for (unsigned i = 0; i < N; ++i) {
fill_val = uni();
exponent = 2.0 + uni();
t.restart();
for (unsigned j = 0; j wrote:
On Wed, Jul 7, 2010 at 4:45 PM, Max S. Kaznady
wrote:
Great example, thanks! Before your post, I wrote something like:
namespace my {
void my_vec_pow(boost::numeric::ublas::vector<double> &vec, double expon)
{
boost::numeric::ublas::vector<double>::iterator it;
for (it = vec.begin(); it != vec.end(); ++it)
*it = std::pow(*it, expon);
}
}
I'm just curious, but from performance perspective, how much more
efficient is it to use iterators and functors? I would assume that
most compilers can exploit this to improve memory management and speed
up the computation... I'll write some benchmarks later on this week,
efficiency is crucial for what I'm doing.
By and large, in my experience, it's barely measurable, at least for
simple cases. When you write
for(i=v.begin(); i!=v.end();....)
v.end() is evaluated on each iteration, which may have some effect,
depending
on type of v, but for simple vectors it's probably inlined anyway. (there's
folks
on here that know much more about it than I do).
With the new Boost.Range algorithms there's also some syntactic
simplification
as you can write
for(v,fn);
which is a real plus if v is complicated expression, since you don't have to
evaluate
it twice or even put it in an explicit variable.
Cheers
- Rob.
_______________________________________________
Boost-users mailing list
Boost-users@lists.boost.org
http://lists.boost.org/mailman/listinfo.cgi/boost-users