Boost logo

Boost Users :

Subject: [Boost-users] Why use 'functional composition' with boost::bind
From: Wolfram Brenig (w.brenig_at_[hidden])
Date: 2010-08-03 10:35:46


Hi,

as a casual/newbie boost-user I'm trying to come to
grips with why/when nested functional composition with
boost::bind might be of help - following Karlsson's
book, in its fifth printing.

----------------------
1) It seems that he suggests, that if one is into
doing something like

T faa(int);
T foo(const& T);

int i;
vector<T> v(10); // container
for(i=0; i<10; i++) {
  v[i] = faa(i); // set it
  v[i] = foo(v[i]); // operate on it
}

then instead of the 'old style' for-loops it would be
more desirable to use 'STL style' algorithms with
the functors created from boost::bind, say like

transform(v.begin(),v.end(),boost::bind(&foo,_1));

instead of the 2nd line in the previous for-loop, a.s.o.

----------------------
2) Then, it is suggested furthermore that if foo()
is a (simple?) nested function, say T=double and,

double foo(double x) { return 2*x; }

then one should do 'functional composition', i.e. create the
functor for the previous point 1) by

boost::bind(std::multiplies<double>(),2,_1)

(Karlsson has this example on adding and subtracting 10%.)

-----------------------
Now, my question is, if such nested construction of functors
at the call site, which is suggested also in the 'Bind Summary'
in Karlsson's book, will actually lead to code which has no
relevant performance loss as compared to 'old-style' for-loops?

To check this, I do something rather simple (simplistic?).
Namely, I take a vector of some size, then I fill it with
random doubles between 0 and M_PI, then (stupid me) I test
if sin(v[i])^2 + cos([i])^2 = 1 for each element of the
vector. The code is attached.

I do this many many times and measure the runtime.

I do this for two cases:

case 1: with old-style for loops
case 3: with STL algorithms and boost::bind

At least for this little program, the old-style code is
faster by roughly a factor of 2
as compared to the boost::bind approach.
(compiled with gcc version 4.3.2 and optimized)
Case 2 just to show that this is not due to more sin()/cos()
function calls within the nested functor, since it is also a
factor of 2 faster than then the boost variant.

So, in conclusion: when/how should I really use functional composition?

wb

---------------------------------
#include <iostream>
#include <vector>
#include <math.h>
#include <algorithm>
#include "boost/lambda/bind.hpp"
#include <assert.h>
#include <time.h>

using namespace std;
using namespace boost;
using namespace boost::lambda;

int main() {
  int in;
  unsigned n(1000),i,l;
  double x,y,t;
  vector<double> v(n);
  clock_t start, stop;

  cin >> in; // tell me which case to check

  switch(in) {
  case 0: // Check how much time is lost in loops and math
    // ---------------------------------------------------------------------------
    t = 0.0;
    assert((start = clock())!=-1);
     for(l=0;l<10000;l++) {
      for(i=0;i<n;i++) {
        x= (random()*M_PI)/RAND_MAX;
        x = sin(v[i]); x = x*x;
        y = cos(v[i]); y = y*y;
        x = x + y;
      }
     }
     stop = clock();
     t = (double) (stop-start)/CLOCKS_PER_SEC;
     cout << t << endl;
  break;

  case 1: // Time for old-style for-loops
    // ---------------------------------------------------------------------------
    t = 0.0;
    assert((start = clock())!=-1);
     for(l=0;l<10000;l++) {
      for(i=0;i<n;i++) {
        v[i] = (random()*M_PI)/RAND_MAX;
        x = sin(v[i]); x = x*x;
        y = cos(v[i]); y = y*y;
        v[i] = x + y;
      }
     }
     stop = clock();
     t = (double) (stop-start)/CLOCKS_PER_SEC;
     cout << t << endl;
  break;

  case 2: // Time for old-style for-loops and unnecessarily many sin/cos calls
    // ---------------------------------------------------------------------------
    t = 0.0;
    assert((start = clock())!=-1);
    for(l=0;l<10000;l++) {
      for(i=0;i<n;i++) {
        v[i] = (random()*M_PI)/RAND_MAX;
        v[i] = sin(v[i])*sin(v[i])+cos(v[i])*cos(v[i]);
      }
    }
    stop = clock();
    t = (double) (stop-start)/CLOCKS_PER_SEC;
    cout << t << endl;
    break;

  case 3: // Time needed with nested boost::bind and STL iteration over container
    // ---------------------------------------------------------------------------
    t = 0.0;
    assert((start = clock())!=-1);
    for(l=0;l<10000;l++) {
      generate(v.begin(),v.end(),
               bind(multiplies<double>(),
                    bind(&random),
                    M_PI/RAND_MAX));
      
      transform(v.begin(),v.end(),v.begin(),
                bind(plus<double>(),
                     bind(multiplies<double>(),bind(sin,_1),bind(sin,_1)),
                     bind(multiplies<double>(),bind(cos,_1),bind(cos,_1))
                     )
                );
    }
    stop = clock();
    t = (double) (stop-start)/CLOCKS_PER_SEC;
    cout << t << endl;
    break;

  }
  
  return EXIT_SUCCESS;
}


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