在 Rcpp 中选择 NumericVector 和 arma::vec
Deciding between NumericVector and arma::vec in Rcpp
使用 RcppArmadillo,使用 arma::vec
从 R 到 Rcpp 的转换与使用 Rcpp 和 NumericVector
一样简单。我的项目使用 RcppArmadillo。
我不确定使用什么,NumericVector
或 arma::vec
?这两者之间的主要区别是什么?什么时候使用哪个?使用一个比另一个有 performance/memory 优势吗?唯一的区别是成员函数吗?而且,作为奖励问题:我应该考虑 arma::colvec
还是 arma::rowvec
?
What are the key differences between those two?
*Vector
和 *Matrix
classes in Rcpp 充当 R 的包装器的 SEXP 表示,例如作为数据指针的 S 表达式。有关详细信息,请参阅 Section 1.1 SEXPs of R Internals。Rcpp 的设计通过从 classes 构造 C++ 对象来利用这一点指向数据的指针。这促进了两个关键特性:
- R和C++对象之间的无缝转换,以及
- 低 R 和 C++ 之间的传输成本,因为只传递一个指针。
- 因为数据不是复制的,而是引用的
同时,arma
对象类似于传统的 std::vector<T>
,因为 deep 复制发生在 R 和 C++ 对象。此语句有一个例外,advanced constructor 的存在允许 R 对象后面的内存被 重用 armadillo
对象的结构内部。因此,如果你不小心,你可能会在从 R 到 C++ 的转换过程中招致不必要的惩罚,反之亦然。
注意: arma::sp_mat
不存在允许内存重用的高级构造函数。因此,在从 R 到 C++[ 执行复制时,使用具有稀疏矩阵的引用可能 not 产生所需的加速=197=] 并返回。
您可以查看主要基于 "pass-by-reference" 或 "pass-by-copy" 范例的差异。要了解代码之外的差异,请考虑 mathwarehouse 的以下 GIF:
为了在代码中说明这种情况,请考虑以下三个函数:
#include <RcppArmadillo.h>
// [[Rcpp::depends(RcppArmadillo)]]
// [[Rcpp::export]]
void memory_reference_double_ex(arma::vec& x, double value) {
x.fill(value);
}
// [[Rcpp::export]]
void memory_reference_int_ex(arma::ivec& x, int value) {
x.fill(value);
}
// [[Rcpp::export]]
arma::vec memory_copy_ex(arma::vec x, int value) {
x.fill(value);
return x;
}
两个函数memory_reference_double_ex()
和memory_reference_int_ex()
将更新inside of R的对象R假设适当的数据类型存在。因此,我们可以通过在定义中指定 void
来避免 returning 一个值,因为 x
分配的内存被 重用 .第三个函数 memory_copy_ex()
需要一个 return 类型,因为它 passes-by-copy 因此 不会 在没有重新分配调用的情况下修改现有存储。
强调一下:
x
向量将通过引用传递到 C++,例如&
在 arma::vec&
或 arma::ivec&
的末尾,以及
x
在 R 中的 class 是 double
或 integer
意味着我们匹配的基础类型arma::vec
, e.g Col<double>
, or arma::ivec
,例如Col<int>
.
让我们快速看两个例子。
在第一个示例中,我们将查看 运行 memory_reference_double_ex()
的结果并将其与 memory_copy_ex()
生成的结果进行比较。请注意,R 和 C++ 中定义的对象之间的类型是相同的(例如 double
)。在下一个示例中,这将 not 成立。
x = c(0.1, 2.3, 4.8, 9.1)
typeof(x)
# [1] "double"
x
# [1] 0.1 2.3 4.8 9.1
# Nothing is returned...
memory_reference_double_ex(x, value = 9)
x
# [1] 9 9 9 9
a = memory_copy_ex(x, value = 3)
x
# [1] 9 9 9 9
a
# [,1]
# [1,] 3
# [2,] 3
# [3,] 3
# [4,] 3
现在,如果 R 对象的基础类型是 integer
而不是 double
会怎样?
x = c(1L, 2L, 3L, 4L)
typeof(x)
# [1] "integer"
x
# [1] 1 2 3 4
# Return nothing...
memory_reference_double_ex(x, value = 9)
x
# [1] 1 2 3 4
发生了什么事?为什么 x
没有更新?好吧,在幕后 Rcpp 创建了一个正确类型的新内存分配 -- double
而不是 int
-- 在将它传递给 armadillo
.这导致两个对象之间的引用 "linkage" 不同。
如果我们更改为在 armadillo
向量中使用整数数据类型,请注意我们现在具有与之前给出的相同效果:
memory_reference_int_ex(x, value = 3)
x
# [1] 3 3 3 3
这引发了对这两种范式的有用性的讨论。由于 speed 是使用 C++ 时的首选基准,让我们从基准的角度来看待它。
考虑以下两个函数:
#include <RcppArmadillo.h>
// [[Rcpp::depends(RcppArmadillo)]]
// [[Rcpp::export]]
void copy_double_ex(arma::vec x, double value) {
x.fill(value);
}
// [[Rcpp::export]]
void reference_double_ex(arma::vec& x, double value) {
x.fill(value);
}
运行 对它们的微基准测试产生:
# install.packages("microbenchmark")
library("microbenchmark")
x = rep(1, 1e8)
micro_timings = microbenchmark(copy_double_ex(x, value = 9.0),
reference_double_ex(x, value = 9.0))
autoplot(micro_timings)
micro_timings
# Unit: milliseconds
# expr min lq mean median uq max neval
# copy_double_ex(x, value = 9) 523.55708 529.23219 547.22669 536.71177 555.00069 640.5020 100
# reference_double_ex(x, value = 9) 79.78624 80.70757 88.67695 82.44711 85.73199 308.4219 100
注意:引用的对象每次迭代比复制的范例快 6.509771 倍,就像我们所做的那样不需要必须重新分配和填充那段记忆
When to use which?
你需要做什么?
您是否只是想快速加速依赖于循环但不需要严格的线性代数操作的算法?
如果是这样,只需使用 Rcpp 就足够了。
您是否正在尝试执行线性代数运算?
或者您是否希望跨多个库或计算平台(例如 MATLAB、Python、R、...)使用此代码?
如果是这样,您应该在 armadillo 中编写算法的关键,并设置适当的挂钩以将函数导出到 R 与 Rcpp.
Is there a performance/memory advantage of using one over the other?
是的,如前所述,绝对 具有性能/内存优势。不仅如此,通过使用 RcppArmadill 您实际上是在 Rcpp 之上添加了一个额外的库,因此增加了整体安装占用空间、编译时间和系统要求(请参阅 macOS 构建的问题)。弄清楚您的项目需要什么,然后选择该结构。
Are the only difference the member functions?
不仅是成员函数,还有:
- 矩阵分解方面的估计例程
- 计算统计量值
- 对象生成
- 稀疏表示(避免操作 S4 对象)
这些是 Rcpp 和 armadillo 之间的基本 差异。一个是为了促进 R 对象到 C++ 的转移,而另一个是为了更严格的线性代数计算。这在很大程度上应该是显而易见的,因为 Rcpp 不 实现任何矩阵乘法逻辑,而 armadillo
使用系统的基本线性代数子程序 (BLAS)执行计算。
And, as a bonus question: should I even consider arma::colvec or arma::rowvec?
取决于您希望如何 returned 结果。您想要一个:1 x N
(行向量)还是 N x 1
(列向量)? RcppArmadillo
默认情况下 return 将这些结构作为具有适当尺寸的 矩阵 对象和 不是 传统的一维 R向量.
举个例子:
#include <RcppArmadillo.h>
// [[Rcpp::depends(RcppArmadillo)]]
// [[Rcpp::export]]
arma::vec col_example(int n) {
arma::vec x = arma::randu<arma::vec>(n);
return x;
}
// [[Rcpp::export]]
arma::rowvec row_example(int n) {
arma::rowvec x = arma::randu<arma::rowvec>(n);
return x;
}
测试:
set.seed(1)
col_example(4)
# [,1]
# [1,] 0.2655087
# [2,] 0.3721239
# [3,] 0.5728534
# [4,] 0.9082078
set.seed(1)
row_example(4)
# [,1] [,2] [,3] [,4]
# [1,] 0.2655087 0.3721239 0.5728534 0.9082078
@coatless 的回答是正确的,但向您提供了您没有要求的详细信息。
同时,您的问题未明确说明,因为您没有说明您需要这些载体的用途。有了这个警告,我会说
- 对于简单的用例,Rcpp 很好,RcppArmadillo 同样很好
- 对于需要线性代数的用例,首选 RcppArmadillo
- 性能将大致相同,但需要注意的是,如上文所述,您需要为 RcppArmadillo 明确 'call-by-reference'
- 只读向量访问(例如,像
sum()
或 min()
或查找这样的缩减)和读写访问之间也有很大的区别 return修改后的向量
- 所有用例通常都比 R 代码快得多,因此首先不要纠结于此。
一旦你做对了,你就可以(也许应该)分析。
使用 RcppArmadillo,使用 arma::vec
从 R 到 Rcpp 的转换与使用 Rcpp 和 NumericVector
一样简单。我的项目使用 RcppArmadillo。
我不确定使用什么,NumericVector
或 arma::vec
?这两者之间的主要区别是什么?什么时候使用哪个?使用一个比另一个有 performance/memory 优势吗?唯一的区别是成员函数吗?而且,作为奖励问题:我应该考虑 arma::colvec
还是 arma::rowvec
?
What are the key differences between those two?
*Vector
和 *Matrix
classes in Rcpp 充当 R 的包装器的 SEXP 表示,例如作为数据指针的 S 表达式。有关详细信息,请参阅 Section 1.1 SEXPs of R Internals。Rcpp 的设计通过从 classes 构造 C++ 对象来利用这一点指向数据的指针。这促进了两个关键特性:
- R和C++对象之间的无缝转换,以及
- 低 R 和 C++ 之间的传输成本,因为只传递一个指针。
- 因为数据不是复制的,而是引用的
同时,arma
对象类似于传统的 std::vector<T>
,因为 deep 复制发生在 R 和 C++ 对象。此语句有一个例外,advanced constructor 的存在允许 R 对象后面的内存被 重用 armadillo
对象的结构内部。因此,如果你不小心,你可能会在从 R 到 C++ 的转换过程中招致不必要的惩罚,反之亦然。
注意: arma::sp_mat
不存在允许内存重用的高级构造函数。因此,在从 R 到 C++[ 执行复制时,使用具有稀疏矩阵的引用可能 not 产生所需的加速=197=] 并返回。
您可以查看主要基于 "pass-by-reference" 或 "pass-by-copy" 范例的差异。要了解代码之外的差异,请考虑 mathwarehouse 的以下 GIF:
为了在代码中说明这种情况,请考虑以下三个函数:
#include <RcppArmadillo.h>
// [[Rcpp::depends(RcppArmadillo)]]
// [[Rcpp::export]]
void memory_reference_double_ex(arma::vec& x, double value) {
x.fill(value);
}
// [[Rcpp::export]]
void memory_reference_int_ex(arma::ivec& x, int value) {
x.fill(value);
}
// [[Rcpp::export]]
arma::vec memory_copy_ex(arma::vec x, int value) {
x.fill(value);
return x;
}
两个函数memory_reference_double_ex()
和memory_reference_int_ex()
将更新inside of R的对象R假设适当的数据类型存在。因此,我们可以通过在定义中指定 void
来避免 returning 一个值,因为 x
分配的内存被 重用 .第三个函数 memory_copy_ex()
需要一个 return 类型,因为它 passes-by-copy 因此 不会 在没有重新分配调用的情况下修改现有存储。
强调一下:
x
向量将通过引用传递到 C++,例如&
在arma::vec&
或arma::ivec&
的末尾,以及x
在 R 中的 class 是double
或integer
意味着我们匹配的基础类型arma::vec
, e.gCol<double>
, orarma::ivec
,例如Col<int>
.
让我们快速看两个例子。
在第一个示例中,我们将查看 运行 memory_reference_double_ex()
的结果并将其与 memory_copy_ex()
生成的结果进行比较。请注意,R 和 C++ 中定义的对象之间的类型是相同的(例如 double
)。在下一个示例中,这将 not 成立。
x = c(0.1, 2.3, 4.8, 9.1)
typeof(x)
# [1] "double"
x
# [1] 0.1 2.3 4.8 9.1
# Nothing is returned...
memory_reference_double_ex(x, value = 9)
x
# [1] 9 9 9 9
a = memory_copy_ex(x, value = 3)
x
# [1] 9 9 9 9
a
# [,1]
# [1,] 3
# [2,] 3
# [3,] 3
# [4,] 3
现在,如果 R 对象的基础类型是 integer
而不是 double
会怎样?
x = c(1L, 2L, 3L, 4L)
typeof(x)
# [1] "integer"
x
# [1] 1 2 3 4
# Return nothing...
memory_reference_double_ex(x, value = 9)
x
# [1] 1 2 3 4
发生了什么事?为什么 x
没有更新?好吧,在幕后 Rcpp 创建了一个正确类型的新内存分配 -- double
而不是 int
-- 在将它传递给 armadillo
.这导致两个对象之间的引用 "linkage" 不同。
如果我们更改为在 armadillo
向量中使用整数数据类型,请注意我们现在具有与之前给出的相同效果:
memory_reference_int_ex(x, value = 3)
x
# [1] 3 3 3 3
这引发了对这两种范式的有用性的讨论。由于 speed 是使用 C++ 时的首选基准,让我们从基准的角度来看待它。
考虑以下两个函数:
#include <RcppArmadillo.h>
// [[Rcpp::depends(RcppArmadillo)]]
// [[Rcpp::export]]
void copy_double_ex(arma::vec x, double value) {
x.fill(value);
}
// [[Rcpp::export]]
void reference_double_ex(arma::vec& x, double value) {
x.fill(value);
}
运行 对它们的微基准测试产生:
# install.packages("microbenchmark")
library("microbenchmark")
x = rep(1, 1e8)
micro_timings = microbenchmark(copy_double_ex(x, value = 9.0),
reference_double_ex(x, value = 9.0))
autoplot(micro_timings)
micro_timings
# Unit: milliseconds
# expr min lq mean median uq max neval
# copy_double_ex(x, value = 9) 523.55708 529.23219 547.22669 536.71177 555.00069 640.5020 100
# reference_double_ex(x, value = 9) 79.78624 80.70757 88.67695 82.44711 85.73199 308.4219 100
注意:引用的对象每次迭代比复制的范例快 6.509771 倍,就像我们所做的那样不需要必须重新分配和填充那段记忆
When to use which?
你需要做什么?
您是否只是想快速加速依赖于循环但不需要严格的线性代数操作的算法?
如果是这样,只需使用 Rcpp 就足够了。
您是否正在尝试执行线性代数运算? 或者您是否希望跨多个库或计算平台(例如 MATLAB、Python、R、...)使用此代码?
如果是这样,您应该在 armadillo 中编写算法的关键,并设置适当的挂钩以将函数导出到 R 与 Rcpp.
Is there a performance/memory advantage of using one over the other?
是的,如前所述,绝对 具有性能/内存优势。不仅如此,通过使用 RcppArmadill 您实际上是在 Rcpp 之上添加了一个额外的库,因此增加了整体安装占用空间、编译时间和系统要求(请参阅 macOS 构建的问题)。弄清楚您的项目需要什么,然后选择该结构。
Are the only difference the member functions?
不仅是成员函数,还有:
- 矩阵分解方面的估计例程
- 计算统计量值
- 对象生成
- 稀疏表示(避免操作 S4 对象)
这些是 Rcpp 和 armadillo 之间的基本 差异。一个是为了促进 R 对象到 C++ 的转移,而另一个是为了更严格的线性代数计算。这在很大程度上应该是显而易见的,因为 Rcpp 不 实现任何矩阵乘法逻辑,而 armadillo
使用系统的基本线性代数子程序 (BLAS)执行计算。
And, as a bonus question: should I even consider arma::colvec or arma::rowvec?
取决于您希望如何 returned 结果。您想要一个:1 x N
(行向量)还是 N x 1
(列向量)? RcppArmadillo
默认情况下 return 将这些结构作为具有适当尺寸的 矩阵 对象和 不是 传统的一维 R向量.
举个例子:
#include <RcppArmadillo.h>
// [[Rcpp::depends(RcppArmadillo)]]
// [[Rcpp::export]]
arma::vec col_example(int n) {
arma::vec x = arma::randu<arma::vec>(n);
return x;
}
// [[Rcpp::export]]
arma::rowvec row_example(int n) {
arma::rowvec x = arma::randu<arma::rowvec>(n);
return x;
}
测试:
set.seed(1)
col_example(4)
# [,1]
# [1,] 0.2655087
# [2,] 0.3721239
# [3,] 0.5728534
# [4,] 0.9082078
set.seed(1)
row_example(4)
# [,1] [,2] [,3] [,4]
# [1,] 0.2655087 0.3721239 0.5728534 0.9082078
@coatless 的回答是正确的,但向您提供了您没有要求的详细信息。
同时,您的问题未明确说明,因为您没有说明您需要这些载体的用途。有了这个警告,我会说
- 对于简单的用例,Rcpp 很好,RcppArmadillo 同样很好
- 对于需要线性代数的用例,首选 RcppArmadillo
- 性能将大致相同,但需要注意的是,如上文所述,您需要为 RcppArmadillo 明确 'call-by-reference'
- 只读向量访问(例如,像
sum()
或min()
或查找这样的缩减)和读写访问之间也有很大的区别 return修改后的向量 - 所有用例通常都比 R 代码快得多,因此首先不要纠结于此。
一旦你做对了,你就可以(也许应该)分析。