Skip to content

[Rcpp-devel] Cost of function pointer dereferencing

5 messages · Romain Francois, Davor Cubranic

#
Hello, 

Since I've been playing with efficiency of various operators for numeric vectors (+,-,/,*), I'm now looking at other functions such as exp, sqrt, etc ...

The way we currently implement exp for vectors requires that we keep a function pointer for the atomic exp and that we dereference the function pointer each time. It turns out that this has some cost : 

     test replications elapsed relative user.self sys.self user.child sys.child
1  static            1   0.691  1.00000     0.691    0.000          0         0
2 pointer            1  11.157 16.14616    11.144    0.012          0         0

Reproduced by this code: 

require( Rcpp )
require( inline )

fx <- cxxfunction( , '
double x ;
for( int j=0; j<1000; j++){
for( int i=0; i<1000000; i++){
    x = exp(2.0) ; 
}
}
return wrap( x ) ;
', plugin = "Rcpp" )

inc <- '
    double (*fun)(double) = exp ;
'
fy <- cxxfunction( , '
double x ;
for( int j=0; j<1000; j++){
for( int i=0; i<1000000; i++){
    x = (*fun)(2.0) ; 
}
}
return wrap( x ) ;
', plugin = "Rcpp", includes = inc )


require( rbenchmark )
benchmark( 
    order = "relative", 
    static = fx(), 
    pointer = fy(),
    replications = 1L
)

Romain
#
I tried using a functor, calling 'exp' in its operator(), and it was even faster than static (albeit very slightly):

      test replications elapsed  relative user.self sys.self user.child
3  functor            1   0.465  1.000000     0.464    0.000          0
1   static            1   0.472  1.015054     0.464    0.001          0
2  pointer            1  15.855 34.096774    15.815    0.007          0

Just add 'fw' below to your benchmark:

incw <- '
   class Ftor {
   public:
     inline double operator () (double x) const {
       return exp(x);
     }
   };
   const Ftor ftor = Ftor();
'
fw <- cxxfunction( , '
double x ;
for( int j=0; j<1000; j++){
for( int i=0; i<1000000; i++){
   x = ftor(2.0) ; 
}
}
return wrap( x ) ;
', plugin = "Rcpp", includes = incw , verbose=TRUE)

Maybe you could create a functor for each basic function and use that in a common loop implementation.

I also tried templatizing Ftor on the function it delegates to, so that you could do something like Ftor<exp>(), but I couldn't get it to compile. Your template-fu is far better than mine, so maybe you'll be able to wrangle it into shape and have even more succint code.

Davor
On 2010-12-23, at 8:45 AM, <romain at r-enthusiasts.com> <romain at r-enthusiasts.com> wrote:

            
#
Le 23 d?c. 2010 ? 06:54 PM, Davor Cubranic <cubranic at stat.ubc.ca> a ?crit :
Intriguing.
My plan is to go through macros
#
After a bit of googling, I figured out how to write the templated functor. It's very simple:

incw <- '
 template<double Func(double) >
 struct Ftor {
   inline double operator () (double x) const {
     return Func(x);
   }
 };
 Ftor<exp> ftor;
'
fw <- cxxfunction( , '
double x ;
for( int j=0; j<1000; j++){
for( int i=0; i<1000000; i++){
 x = ftor(2.0) ; 
}
}
return wrap( x ) ;
', plugin = "Rcpp", includes = incw , verbose=TRUE)

And it's still (insignificantly) faster than the static call:

     test replications elapsed  relative user.self sys.self user.child
3 functor            1   0.463  1.000000     0.464    0.001          0
1  static            1   0.468  1.010799     0.464    0.000          0
2 pointer            1  15.960 34.470842    15.824    0.017          0

Davor
On 2010-12-23, at 9:54 AM, Davor Cubranic wrote:

            
#
On 2010-12-23, at 10:03 AM, romain at r-enthusiasts.com wrote:

            
I guess it is simpler, but oh so old-fashioned. :-)

Davor