如何使用Rcpp将R函数转换为C ++函数? [英] How to convert a R function into a C++ function using Rcpp?

查看:48
本文介绍了如何使用Rcpp将R函数转换为C ++函数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我定义了以下功能:

pij = function(vec){
  out = vec %*% t(vec)
  diag(out) = NA
  out = sum(out, na.rm = T)
  return(out)
}

其中 vec 是向量,例如 vec = rnorm(10 ^ 4,0,1).

我想知道如何使用Rcpp软件包用C ++编写此函数.

I would like to know how this function can be written in C++ using the Rcpp package.

推荐答案

我建议先考虑问题背后的数学原理.对于向量 v ,您正在尝试计算

I would suggest thinking about the math behind the problem first. For a vector v you are trying to calculate

sum_{i=1}^{N-1} sum_{j=i+1}^{N} 2 * v_i * v_j

您可以先创建矩阵 v_i * v_j 来做到这一点,但是如果 v 很大,那可能会很昂贵.因此,更容易直接在C ++中实现双重和:

You can do that by creating the matrix v_i * v_j first, but that can be expensive if v is large. So it is easier to implement the double sum directly in C++:

#include <Rcpp.h>
// [[Rcpp::export]]
double pij_cpp(Rcpp::NumericVector vec) {
  double out{0.0};
  int N = vec.size();
  for (int i = 0; i < N; ++i) {
    for (int j = i + 1; j < N; ++j) {
      out += 2 * vec[i] * vec[j];
    }
  }
  return out;
}

但是,上面的公式实际上可以重新排列:

However, the formula above can actually be rearranged:

2 * sum_{i=1}^{N-1} v_i * sum_{j=i+1}^{N} v_j

这使我们可以从高端开始到低端摆脱双重循环:

which allows us to get rid of the double loop by starting at the high end and going to the low end:

#include <Rcpp.h>
// [[Rcpp::export]]
double pij_opt(Rcpp::NumericVector vec) {
  double out{0.0};
  double sum{0.0};
  int N = vec.size();
  for (int i = N -1; i > 0; --i) {
    sum += vec[i];
    out += sum * vec[i-1];
  }
  return 2 * out;
}

我们可以将这些版本与您的R代码以及基于Armadillo的版本进行比较,以得到长度为 10 ^ 4 的向量:

We can compare these versions with your R code and an Armadillo based version for a vector of length 10^4:

> bench::mark(pij(vec), pij_cpp(vec), pij_opt(vec), pij_arma(vec))
# A tibble: 4 x 14
  expression     min    mean  median     max `itr/sec` mem_alloc  n_gc n_itr total_time result
  <chr>      <bch:t> <bch:t> <bch:t> <bch:t>     <dbl> <bch:byt> <dbl> <int>   <bch:tm> <list>
1 pij(vec)   716.4ms 716.4ms 716.4ms 716.4ms      1.40    1.49GB     1     1      716ms <dbl …
2 pij_cpp(v…  59.9ms  61.4ms  61.5ms  62.3ms     16.3     2.49KB     0     9      552ms <dbl …
3 pij_opt(v…  14.2µs  15.6µs  14.9µs 864.5µs  64072.      2.49KB     0 10000      156ms <dbl …
4 pij_arma(… 834.5ms 834.5ms 834.5ms 834.5ms      1.20    2.49KB     0     1      834ms <dbl …
# ... with 3 more variables: memory <list>, time <list>, gc <list>

R和Armadillo差不多(可能受内存分配限制).第一个C ++版本的速度提高了10倍,第二个版本的速度提高了50000!

R and Armadillo are about on par (and probably limited by the memory allocation). The first C++ version is faster by a factor of 10, the second by a factor of 50000!

完整代码:

// [[Rcpp::depends(RcppArmadillo)]]
#include <RcppArmadillo.h>

// [[Rcpp::export]]
double pij_arma(arma::vec vec) {
  arma::mat out = vec * vec.t();
  out.diag().zeros();
  return arma::accu(out);
}

// [[Rcpp::export]]
double pij_cpp(Rcpp::NumericVector vec) {
  double out{0.0};
  int N = vec.size();
  for (int i = 0; i < N; ++i) {
    for (int j = i + 1; j < N; ++j) {
      out += 2 * vec[i] * vec[j];
    }
  }
  return out;
}

// [[Rcpp::export]]
double pij_opt(Rcpp::NumericVector vec) {
  double out{0.0};
  double sum{0.0};
  int N = vec.size();
  for (int i = N -1; i > 0; --i) {
    sum += vec[i];
    out += sum * vec[i-1];
  }
  return 2 * out;
}


/*** R
pij = function(vec){
  out = vec %*% t(vec)
  diag(out) = NA
  out = sum(out, na.rm = T)
  return(out)
}


set.seed(42)
vec = rnorm(10^4,0,1)
pij(vec)

bench::mark(pij(vec), pij_cpp(vec), pij_opt(vec), pij_arma(vec))
*/

出于完整性考虑:这实际上是算法问题,因此即使R中的 for 循环也比 pij_cpp 更快:

For completeness: This is really a question of algorithm, so even a for loop in R is faster than pij_cpp:

pij_opt_r <- function(vec) {
  out <- 0
  sum <- 0
  N <- length(vec)
  for (i in seq.int(from = N, to = 2, by = -1)) {
    sum <- sum + vec[i]
    out <- out + sum * vec[i-1]
  }
  2 * out
}

在R中使用向量化函数甚至更快,但仍不及 pij_opt :

Using vectorized functions in R is even faster, but still not as fast as pij_opt:

pij_opt_r2 <- function(vec) {
  N <- length(vec)
  vec <- rev(vec)
  sums <- cumsum(vec)
  2 * sum(vec[2:N] * sums[1:N-1])
}

完全基准:

> bench::mark(pij(vec), pij_cpp(vec), pij_opt(vec), pij_opt_r(vec), pij_opt_r2(vec), pij_arma(vec))
# A tibble: 6 x 14
  expression     min     mean   median     max `itr/sec` mem_alloc  n_gc n_itr total_time result
  <chr>      <bch:t> <bch:tm> <bch:tm> <bch:t>     <dbl> <bch:byt> <dbl> <int>   <bch:tm> <list>
1 pij(vec)   733.6ms  733.6ms  733.6ms 733.6ms      1.36    1.49GB     1     1      734ms <dbl …
2 pij_cpp(v…    60ms  61.41ms  60.84ms  64.2ms     16.3     2.49KB     0     9      553ms <dbl …
3 pij_opt(v…  14.2µs  15.83µs  15.35µs 750.1µs  63164.      2.49KB     0 10000      158ms <dbl …
4 pij_opt_r… 981.1µs   1.04ms   1.02ms   1.5ms    960.     119.2KB     0   480      500ms <dbl …
5 pij_opt_r…   157µs 272.95µs 241.57µs  66.3ms   3664.    547.28KB     1  1832      500ms <dbl …
6 pij_arma(… 878.4ms 878.38ms 878.38ms 878.4ms      1.14    2.49KB     0     1      878ms <dbl …
# ... with 3 more variables: memory <list>, time <list>, gc <list>

这篇关于如何使用Rcpp将R函数转换为C ++函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆