Boost logo

Boost Users :

Subject: Re: [Boost-users] apply std::pow to vector or matrix
From: Max S. Kaznady (max.kaznady_at_[hidden])
Date: 2010-07-11 17:35:41


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 <math.h>
#include <fstream>
#include <ctime> // std::time
#include <boost/bind.hpp>
#include <boost/numeric/ublas/vector.hpp>
#include <boost/numeric/ublas/io.hpp>
#include <boost/timer.hpp> // time the runs
// For random number generation
#include <boost/random/linear_congruential.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/uniform_real.hpp>
#include <boost/random/variate_generator.hpp>
// Sun CC doesn't handle boost::iterator_adaptor yet
#if !defined(__SUNPRO_CC) || (__SUNPRO_CC > 0x530)
#include <boost/generator_iterator.hpp>
#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 <stdio.h>
#include <gsl/gsl_vector.h>

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<base_generator_type&, boost::uniform_real<> > 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<N; ++j) {
      boost_v(j) = std::pow(fill_val, exponent);
    }
    times(i) = t.elapsed();
  }
  std::cout << "Boost manual fill avg time: " << std::endl;
  std::cout << std::fixed << boost::numeric::ublas::sum(times) / (double) N
      << std::endl;

  // Filling GSL by hand
  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<N; ++j) {
      gsl_vector_set(gsl_v, j, std::pow(fill_val, exponent));
    }
    times(i) = t.elapsed();
  }
  std::cout << "GSL avg time: " << std::endl;
  std::cout << std::fixed << boost::numeric::ublas::sum(times) / (double) N
      << std::endl;

  //de-allocate GSL
  gsl_vector_free(gsl_v);
  return 0;
}

Cheers,
Max

On Wed, Jul 7, 2010 at 12:13 PM, Robert Jones <robertgbjones_at_[hidden]> wrote:
> On Wed, Jul 7, 2010 at 4:45 PM, Max S. Kaznady <max.kaznady_at_[hidden]>
> 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_at_[hidden]
> http://lists.boost.org/mailman/listinfo.cgi/boost-users
>


Boost-users list run by williamkempf at hotmail.com, kalb at libertysoft.com, bjorn.karlsson at readsoft.com, gregod at cs.rpi.edu, wekempf at cox.net