三次函数计算的速度优化

时间:2016-05-30 18:03:15

标签: r optimization

我需要在R中多次评估函数(sin(x)/x)^3。 最快的方法是什么:

  1. (sin(x)/x)^3
  2. (sin(x)/x)^3L
  3. { y=sin(x)/x; y*y*y }

2 个答案:

答案 0 :(得分:11)

tl; dr 第三个选项(y*y*y)最快;转换为C ++有一点帮助,但不是我们所期望的数量级(只有大约20%的改进),因为R在向量化时已经非常有效地完成了这项任务。

使用microbenchmark包找出...

library(microbenchmark)
x <- 10
m <- microbenchmark((sin(x)/x)^3,(sin(x)/x)^3L,
                     {y=sin(x)/x; y*y*y}, times=1e4)

## Unit: nanoseconds
##                  expr min   lq     mean median   uq    max neval
##          (sin(x)/x)^3   1 1524 1795.508   1576 1654 220730 10000
##         (sin(x)/x)^3L   1 1503 1766.368   1558 1633 216711 10000
## { y=sin(x)/x; y*y*y }   2 1623 1925.608   1692 1785 243385 10000

现在尝试矢量化版本(适用于长度为10 ^ 5的矢量),包括Rcpp化版本:

set.seed(101)
x <- rnorm(1e5)
library(Rcpp)
sourceCpp("cubebench.cpp")
m2 <- microbenchmark((sin(x)/x)^3,(sin(x)/x)^3L,
                 {y=sin(x)/x; y*y*y},
                 sin_cube(x),
                 sin_cubepow(x), times=100)

## Unit: milliseconds
##                    expr   min     lq   mean median     uq     max
##            (sin(x)/x)^3 9.512 10.284 10.685 10.492 10.785  13.212
##           (sin(x)/x)^3L 9.956 10.480 11.902 10.735 11.125 105.164
##  { y=sin(x)/x;  y*y*y } 2.455  2.855  3.348  3.063  3.541   5.356
##             sin_cube(x) 1.906  2.278  2.611  2.355  2.785   4.732
##          sin_cubepow(x) 8.331  9.180  9.804  9.515  9.960  13.931

令人惊讶的是,对于更长的向量,第三种选择更快。 C ++变体与相应的R版本没有什么不同。

图片:

comb <- rbind(data.frame(w="short",as.data.frame(m)),
              data.frame(w="long",as.data.frame(m2)))
library(ggplot2); theme_set(theme_bw())
library(ggstance)
ggplot(comb,aes(time,expr))+geom_violinh(fill="gray")+
    scale_x_log10()+
    labs(x="time (ns)",y="")+
    facet_grid(.~w,scale="free")

enter image description here

  • 最重要的性能提示是尽可能地向量化您的计算(以这种方式保存至少10倍)
  • 如果每次计算1000到10000纳秒之间的差异对您很重要,您可能需要使用“更接近金属”的计算平台(C / C ++ / Rcpp或Julia或......)

这是cubebench.cpp

// Hacked from http://gallery.rcpp.org/articles/run_sum-benchmark/
#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::export]]
NumericVector sin_cube(NumericVector x) {

    int sz = x.size();
    double y;
    NumericVector res(sz);

    // loop through the vector
    for(int i = 0; i < sz; i++){
        y = sin(x[i])/x[i];
        res[i] = y*y*y;
    }
    return res;
}

// [[Rcpp::export]]
NumericVector sin_cubepow(NumericVector x) {

    int sz = x.size();
    double y;
    NumericVector res(sz);

    // loop through the vector
    for(int i = 0; i < sz; i++){
        y = sin(x[i])/x[i];
        res[i] = pow(y,3.0);
    }
    return res;
}

答案 1 :(得分:3)

几乎可以肯定是三分之二。第三个选项两次调用'*'并进行额外的分配。您可以使用system.time函数进行测试:

> x <- runif(100000)
> system.time( {y=(sin(x)/x)^3 }); system.time( {y=(sin(x)/x)^3L} )
   user  system elapsed 
  0.008   0.001   0.008 
   user  system elapsed 
  0.009   0.001   0.008 
>                      system.time( {y=sin(x)/x; y=y*y*y})
   user  system elapsed 
  0.003   0.001   0.003
好吧,好吧。另一个非常好的理论被事实击毙。