【发布时间】:2016-08-26 15:43:38
【问题描述】:
我在 R 中编写了一个递归函数并使用 memoise 来加速它。我试图通过在 Rcpp 中编写它然后记忆 Rcpp 函数来进一步加快它,但 R 函数更快。为什么会这样,有什么方法可以加快我的使用速度?
require(microbenchmark)
require(Rcpp)
require(memoise)
Rcpp 函数:
cppFunction('
double FunCpp (unsigned int i, double d1, double d2,
double p, double s, unsigned int imax,
double n, double k, double r,
double m, double t) {
if (i == 0) return 0;
if (i == 1) return log2(-1*d1);
if (i == 2) return log2(d2*d1 - p*s);
double x = log2(fabs(-(((imax - (n - i))/imax)*k*r + m + (n - i)*t)));
x = x + FunCpp(i-1, d1, d2, p, s, imax, n, k, r, m, t);
double y = log2((n - i + 1)*t*k*r*((imax - ((n - i + 1) - 1))/imax));
y = y + FunCpp(i-2, d1, d2, p, s, imax, n, k, r, m, t);
return x + log2(1 - pow(2,y-x));
}
')
FunCpp = memoise(FunCpp)
R函数:
FunR = memoise(function(i, d1, d2, p, s, imax, n, k, r, m, t) {
if(i == 0) 0
else if(i == 1) log2(-1*d1)
else if(i == 2) log2(d2*d1 - p*s)
else {
x = log2(abs(-(((imax - (n - i))/imax)*k*r + m + (n - i)*t)))
x = x + FunR(i-1, d1, d2, p, s, imax, n, k, r, m, t)
y = log2((n - i + 1)*t*k*r*((imax - ((n - i + 1) - 1))/imax))
y = y + FunR(i-2, d1, d2, p, s, imax, n, k, r, m, t)
x + log2(1 - 2^(y-x))
}
})
这个速度比较对我来说是现实的。递归函数用于整数范围,但之后,它不会再次使用相同的输入调用。因此,为了比较速度,这里我从其他函数中调用函数,在调用完递归函数后,我使用 forget() 来重置缓存。
TestFunR = function() {
x = sapply(1:31, function(i) {
FunR(i = 31-i, d1 = -152, d2 = -147.33, p = 150, s = 0.03,
imax = 30, n = 31, k = 1, r = 1, m = 2, t = 5)
})
forget(FunR)
}
TestFunCpp = function() {
x = sapply(1:31, function(i) {
FunCpp(i = 31-i, d1 = -152, d2 = -147.33, p = 150, s = 0.03,
imax = 30, n = 31, k = 1, r = 1, m = 2, t = 5)
})
forget(FunCpp)
}
microbenchmark(TestFunR(), TestFunCpp())
Unit: milliseconds
expr min lq mean median uq max neval cld
TestFunR() 9.979738 10.4910 12.83228 10.91887 11.89264 61.61513 100 a
TestFunCpp() 520.955483 528.6965 547.31103 536.73058 547.66377 729.57631 100 b
编辑:在发布之前,我已经从 Dirk 的书中得到了一个可行的方法。
includeText = '
#include <algorithm>
#include <vector>
#include <stdexcept>
#include <cmath>
#include <iostream>
class F {
public:
F(unsigned int n = 200, double d1 = 0, double d2 = 0, double p = 0, double s = 0) {
memo.resize(n);
std::fill( memo.begin(), memo.end(), NAN );
memo[0] = 0;
memo[1] = log2(-1*d1);
memo[2] = log2(d2*d1 - p*s);
}
double FunIL(int i, double d1, double d2, double p, double s, double imax,
double n, double k, double r, double m, double t) {
if (i < 0) return((double) NAN);
if (i >= (int) memo.size()) throw std::range_error(\"i too large\");
if (!std::isnan(memo[i])) return(memo[i]);
double x = log2(fabs(-(((imax - (n - i))/imax)*k*r + m + (n - i)*t)));
x = x + FunIL(i-1, d1, d2, p, s, imax, n, k, r, m, t);
double y = log2((n - i + 1)*t*k*r*((imax - ((n - i + 1) - 1))/imax));
y = y + FunIL(i-2, d1, d2, p, s, imax, n, k, r, m, t);
memo[i] = x + log2(1 - pow(2,y-x));
return(memo[i]);
}
private:
std::vector< double > memo;
};
'
bodyText = '
int is = Rcpp::as<int>(i);
double d1s = Rcpp::as<double>(d1);
double d2s = Rcpp::as<double>(d2);
double ps = Rcpp::as<double>(p);
double ss = Rcpp::as<double>(s);
double imaxs = Rcpp::as<double>(imax);
double ns = Rcpp::as<double>(n);
double ks = Rcpp::as<double>(k);
double rs = Rcpp::as<double>(r);
double ms = Rcpp::as<double>(m);
double ts = Rcpp::as<double>(t);
F f(ns, d1s, d2s, ps, ss);
return Rcpp::wrap( f.FunIL(is, d1s, d2s, ps, ss, imaxs, ns, ks, rs, ms, ts) );
'
FunInline = cxxfunction(signature(i = "integer", d1 = "numeric", d2 = "numeric", p = "numeric",
s = "numeric", imax = "numeric", n = "numeric", k = "numeric",
r = "numeric", m = "numeric", t = "numeric"),
plugin = "Rcpp",
verbose = T,
incl = includeText,
body = bodyText)
它同样有效(参见 TestFunInline):
microbenchmark(TestFunR(), TestFunCpp(), TestFunCpp_Mem(), TestFunInline())
Unit: microseconds
expr min lq mean median uq max neval cld
TestFunR() 8871.251 9067.758 10301.8003 9287.5725 9593.1310 19270.081 100 b
TestFunCpp() 514415.356 517160.251 522431.2980 519321.6130 523811.7640 584812.731 100 c
TestFunCpp_Mem() 245.474 264.291 284.8908 281.6105 292.0885 526.870 100 a
TestFunInline() 279.686 295.723 378.2134 306.8425 316.0370 6621.364 100 a
但是,我无法让它与 doParallel 一起使用。我正在使用 optim 和 optimx 包优化每个进程的目标函数,当我使用 %do% 时它可以工作,但是当我使用 %dopar% 时,我看到的只是目标函数无法在初始参数处进行评估。我从他的许多其他帖子中接受了 Dirk 的建议,并将 Coatless 的方法放入一个包中,但我不确定如何将 Dirk 书中的方法放入一个包中。这只是我在 C++ 方面的经验不足。
编辑 2:它最后点击了如何将 Dirk 的方法放入我的包中的源文件中。我知道还有其他关于将 Rcpp 与 doParallel 一起使用的讨论,但我将这段代码放在这里是因为它是解决我的问题的另一种好方法,并且通过将此代码添加到我的包中的源文件中,它恰好更容易让我让它在我的并行方法中工作,而不是内联。
class F {
public:
F(unsigned int n = 200, double d1 = 0, double d2 = 0, double p = 0, double s = 0) {
memo.resize(n);
std::fill( memo.begin(), memo.end(), NAN );
memo[0] = 0;
memo[1] = log2(-1*d1);
memo[2] = log2(d2*d1 - p*s);
}
double FunIL(int i, double d1, double d2, double p, double s, double imax,
double n, double k, double r, double m, double t) {
if (i < 0) return((double) NAN);
if (i >= (int) memo.size()) throw std::range_error("\"i too large\"");
if (!std::isnan(memo[i])) return(memo[i]);
double x = log2(fabs(-(((imax - (n - i))/imax)*k*r + m + (n - i)*t)));
x = x + FunIL(i-1, d1, d2, p, s, imax, n, k, r, m, t);
double y = log2((n - i + 1)*t*k*r*((imax - ((n - i + 1) - 1))/imax));
y = y + FunIL(i-2, d1, d2, p, s, imax, n, k, r, m, t);
memo[i] = x + log2(1 - pow(2,y-x));
return(memo[i]);
}
private:
std::vector< double > memo;
};
// [[Rcpp::export]]
double FunDirk(int i, double d1, double d2, double p, double s,
double imax, double n, double k, double r,
double m, double t) {
F f(n, d1, d2, p, s);
return f.FunIL(i, d1, d2, p, s, imax, n, k, r, m, t);
}
【问题讨论】:
标签: r performance recursion rcpp memoization