作为 S4 对象的一部分返回时避免复制犰狳矩阵

Avoiding copying Armadillo matrix when returning as part of S4 object

我有一个 Rcpp 函数,它接受一个 S4 栅格对象,将一些数据插入到一个槽中,然后 returns 该对象的一个​​新版本。这是一个最小的代表:

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

// [[Rcpp::export]]
Rcpp::S4 new_raster(Rcpp::S4 raster, int n_elem) {
  Rcpp::S4 r_data(raster.slot("data"));
  r_data.slot("values") = Rcpp::NumericVector(n_elem);        

  // Need help with...
  // creating armadillo vector from `r_data.slot("values")` here
  // arma::vec new_data = ... ?

  return raster;
}

/*** R
library("raster")
r <- raster(res = 0.1)
n <- ncell(r)
r1 <- new_raster(r, n)
head(r1@data@values)
*/

由于返回的数据可能非常大,我想避免创建向量的多个副本。如何在不复制的情况下在 raster@data@values 插槽中创建 Armadillo 矢量?

编辑: 我没有正确理解问题。 objective 是 从 S4 对象中提取 信息并与 Armadillo 对象共享该内存。我保留了这个答案的第一部分,因为它仍然有优点,因为它强调了如何重用 Armadillo 对象内存并重新分配给 NumericVector。不过,考虑到在 Noam 的回应之后问题现在变得清晰起来,第二部分可能更相关。


如果我对问题的理解正确,objective 是重用分配给初始犰狳向量的内存位置(例如零填充)。每个评论的子目标是稍后将其移至 NumericVector

那么,我们开始吧:

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

// [[Rcpp::export]]
Rcpp::NumericVector vec_mem_ex(int n_elem) {

  // Make the initial vector
  arma::vec X(n_elem, arma::fill::zeros);

  // Create a new vector
  arma::vec Y(X.begin(), X.n_elem, false, true);
  // `copy_aux_mem` is set to false, the vector will instead directly
  // use the auxiliary memory (ie. no copying). 
  // This is dangerous in certain circumstances!

  // `strict` is set to true, the vector will be bound to the
  // auxiliary memory for its lifetime; the number of elements 
  // in the vector can't be changed

  // Show memory is shared by modifying value
  Y.fill(42.0);

  // Convert X to a NumericVector
  Rcpp::NumericVector Z = Rcpp::NumericVector(X.begin(), X.end());

  return Z;
}

/***R
(a = vec_mem_ex(5))
*/

这给出:

> (a = vec_mem_ex(5))
[1] 42 42 42 42 42

编辑

要捕捉问题的 S4 性并通过@noam-ross 的回答给出更新,请考虑以下几点:

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

// [[Rcpp::export]]
Rcpp::S4 new_raster(Rcpp::S4 raster, int n_elem) {

  // Embed S4 object for nesting
  Rcpp::S4 r_data(raster.slot("data"));

  // Create obj@data@values
  // Initializes values with a vector of 0's.
  r_data.slot("values") = Rcpp::NumericVector(n_elem);


  // --- The new part...

  // We do _not_ have access to the vector that was stored in r_data.slot("values")

  // Convert from SEXP to NumericVector
  Rcpp::NumericVector temp = Rcpp::NumericVector(r_data.slot("values"));

  // Use the advanced vector ctor of Armadillo to capture the memory location
  arma::vec new_data(
      temp.begin(), // Uses the iterator interface to access the double* requirement
      n_elem,       // Set the size of the vector
      false,        // Avoid copying by disabling `copy_aux_mem`
      true          // Bind memory by enabling `strict`
  );

  // Show memory is shared by modifying value
  new_data.fill(42.0);

  // --- End new

  return raster;
}

/*** R
library("raster")
r <- raster(res = 0.1)
n <- ncell(r)
r1 <- new_raster(r, n)
head(r1@data@values)
*/

我通过为插槽分配一个空但大小合适的 NumericVector 来解决这个问题,然后在其上调用 as() 以找到内存指针以使用高级 Armadillo 构造函数,正如@DirkEddelbuettel 和@coatless 所建议的那样。

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

// [[Rcpp::export]]
Rcpp::S4 new_raster(Rcpp::S4 raster, int n_elem) {
  Rcpp::S4 r_data(raster.slot("data"));
  r_data.slot("values") = Rcpp::NumericVector(n_elem);
  arma::vec new_data(
      Rcpp::as<Rcpp::NumericVector>(r_data.slot("values")).begin(),
      n_elem, false, true
  );
    new_data.randn();
    return raster;
}

/*** R
library("raster")
r <- raster(res = 0.1)
n <- ncell(r)
r1 <- new_raster(r, n)
head(r1@data@values)
*/

结果:

#>[1] -0.26417159 -0.89250080  2.02276338  2.01164847  0.45227281 -0.09313601