我试图更好地了解 Rcpp 代理模型的工作原理。
为此,请考虑以下任务:采样指数随机变量并对结果进行处理。一个天真的 Rcpp 实现可能是
NumericMatrix rmexp1(int n, int d) {
NumericMatrix out(n, d);
NumericVector values;
for (int k=0; k<n; k++) {
values = Rcpp::rexp(d);
// do something with values
out(k, _) = values;
}
return out;
}
Run Code Online (Sandbox Code Playgroud)
以下说法正确吗?
Rcpp::rexp
为新的 R 向量分配空间,然后values
存储对该向量的引用并丢弃它先前持有的引用。values
硬拷贝到其中out(k, _)
。让我们通过实验来解决这个问题。R 分配了多少内存,需要多长时间?首先,让我们使用您的函数并使用不同的参数运行它。我将其包装在 中bench::mark
,因为这为我提供了 RAM 和 CPU 测量值:
> bench::mark(rmexp1(100, 10),
+ rmexp1(100, 100),
+ rmexp1(100, 1000),
+ rmexp1(100, 10000),
+ check = FALSE)
#> # A tibble: 4 x 13
#> expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc
#> <bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl> <int> <dbl>
#> 1 rmexp1(100, 10) 46.93µs 52.61µs 16307. 10.35KB 8.24 7918 4
#> 2 rmexp1(100, 100) 381.41µs 538.42µs 1786. 3.9MB 4.14 863 2
#> 3 rmexp1(100, 1000) 4.83ms 5.08ms 187. 1.53MB 8.68 86 4
#> 4 rmexp1(100, 10000) 59.85ms 63.19ms 15.5 15.27MB 5.17 6 2
#> # … with 5 more variables: total_time <bch:tm>, result <list>, memory <list>,
#> # time <list>, gc <list>
Run Code Online (Sandbox Code Playgroud)
不出所料,更大的矩阵需要更长的时间并需要更多的内存。此外,分配的内存大约是输出矩阵所需内存的两倍。所以是的,我们分配的内存比这里需要的多。
那性能很关键吗?这取决于。毕竟,您正在创建具有指数分布的随机变量,这需要有限的时间。此外,您正在 中进行一些未指定的计算do something with values
,这可能需要更长的时间。让我们通过使用仅分配内存或不将其初始化为零的替代函数来摆脱创建随机变量:
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export]]
NumericMatrix rmzero(int n, int d) {
NumericMatrix out(n, d);
NumericVector values;
for (int k=0; k<n; k++) {
values = Rcpp::NumericVector(d);
// do something with values
out(k, _) = values;
}
return out;
}
// [[Rcpp::export]]
NumericMatrix rmnoinit(int n, int d) {
NumericMatrix out(n, d);
NumericVector values;
for (int k=0; k<n; k++) {
values = Rcpp::NumericVector(Rcpp::no_init(d));
// do something with values
out(k, _) = values;
}
return out;
}
Run Code Online (Sandbox Code Playgroud)
随着bench::mark
我们得到:
> bench::mark(rmexp1(100, 1000),
+ rmzero(100, 1000),
+ rmnoinit(100, 1000),
+ check = FALSE)
#> # A tibble: 3 x 13
#> expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc
#> <bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl> <int> <dbl>
#> 1 rmexp1(100, 1000) 4.83ms 5.05ms 190. 1.53MB 8.72 87 4
#> 2 rmzero(100, 1000) 509.74µs 562.24µs 1510. 1.53MB 60.4 525 21
#> 3 rmnoinit(100, 1000) 404.24µs 469.43µs 1785. 1.53MB 53.8 664 20
#> # … with 5 more variables: total_time <bch:tm>, result <list>, memory <list>,
#> # time <list>, gc <list>
Run Code Online (Sandbox Code Playgroud)
因此,大约只有 1/10 的函数执行时间是由于内存分配和其他开销造成的。其余的来自随机变量。
如果生成随机变量是您代码中的实际瓶颈,您可能对我的dqrng包感兴趣:
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::depends(dqrng)]]
#include <dqrng.h>
// [[Rcpp::export]]
NumericMatrix rmdqexp1(int n, int d) {
NumericMatrix out(n, d);
NumericVector values;
for (int k=0; k<n; k++) {
values = dqrng::dqrexp(d);
// do something with values
out(k, _) = values;
}
return out;
}
Run Code Online (Sandbox Code Playgroud)
随着bench::mark
我们得到:
> bench::mark(rmexp1(100, 1000),
+ rmdqexp1(100, 1000),
+ check = FALSE)
#> # A tibble: 2 x 13
#> expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc
#> <bch:expr> <bch:> <bch:> <dbl> <bch:byt> <dbl> <int> <dbl>
#> 1 rmexp1(100, 1000) 3.69ms 5.03ms 201. 1.53MB 6.36 95 3
#> 2 rmdqexp1(100, 1000) 1.09ms 1.21ms 700. 1.65MB 22.6 310 10
#> # … with 5 more variables: total_time <bch:tm>, result <list>, memory <list>,
#> # time <list>, gc <list>
Run Code Online (Sandbox Code Playgroud)
使用更快的随机数生成器可以节省相当多的时间。