如何有效地将NumericVectors列表组合成一个大的NumericVector?

Alv*_*vin 3 c++ r rcpp

我编写了以下编译的Rcpp代码,但速度并不像预期的那么快.

// [[Rcpp::export]]
NumericVector combine_list_to_vec (const Rcpp::List& list)
{
  int list_size = list.size();
  int large_vec_size = 0;
  IntegerVector start_index(list_size);
  IntegerVector end_index(list_size);
  for (int ii = 0; ii < list_size; ii++)
  {
    NumericVector vec = list[ii];
    start_index[ii] = large_vec_size;
    large_vec_size += vec.size();
    end_index[ii] = large_vec_size - 1;
  }
  NumericVector large_vec(large_vec_size);   // Creating object after getting its size
  for (int ii = 0; ii < list_size; ii++)
  {
    int current_start_index = start_index[ii];
    NumericVector vec = list[ii];
    for (int jj = 0; jj < vec.size(); jj++)
    {
      large_vec[jj + current_start_index] = vec[jj];
    }
  }
  return large_vec;
}
Run Code Online (Sandbox Code Playgroud)

输入变量'list'包含一堆NumericVector,我想将它们组合成一个大的,带有'... tail - head -tail ...'结构.start_index和end_index变量用于方便复制.

微基准测试为特定示例提供以下信息:

x=list();
x[[1]]=runif(1E6);  x[[2]]=runif(1E6);
x[[3]]=runif(1E6);  x[[4]]=runif(1E6);
x[[5]]=runif(1E6);  x[[6]]=runif(1E6);
x[[7]]=runif(1E6);  x[[8]]=runif(1E6);
x[[9]]=runif(1E6);  x[[10]]=runif(1E6);
microbenchmark(combine_list_to_vec(x) -> y)

# Unit: milliseconds
                        expr       min        lq       mean    median        uq       max neval
# y <- combine_list_to_vec(x) 84.166964 84.587516 89.9520601 84.728212 84.871673 349.33234   100
Run Code Online (Sandbox Code Playgroud)

我尝试的另一种方法是调用外部R函数do.call(c,x):

// [[Rcpp::export]]
List combine_list_to_vec (const Rcpp::List& list)
{
  int list_size = list.size();
  int large_vec_size = 0;
  IntegerVector start_index(list_size);
  IntegerVector end_index(list_size);
  for (int ii = 0; ii < list_size; ii++)
  {
    NumericVector vec = list[ii];
    start_index[ii] = large_vec_size;
    large_vec_size += vec.size();
    end_index[ii] = large_vec_size - 1;
  }
  NumericVector large_vec = internal::convert_using_rfunction(list, "sub_do_call");
  List rtn = List::create(large_vec, start_index, end_index);
  return rtn;
}

// The following codes exist as R codes instead of Rcpp
sub_do_call <- function (x)
{
  return (do.call(c, x));
}
Run Code Online (Sandbox Code Playgroud)

速度几乎是以前代码的4倍.有没有办法可以通过在Rcpp和/或RcppArmadillo中使用指针或其他工具来加速组合操作,或者只是在Rcpp中编写do.call(c,x)而不是在外部调用它?谢谢.

Kev*_*hey 9

如果我理解正确的话,你基本上是问,"我怎么能写base::unlistRcpp?" 而且,既然base::unlist是一个.Internal函数(它有一个C实现),你就不太可能做得更好Rcpp.

但是,无论如何,让我们尝试,为了好玩.这是我将使用的类似于你的实现,但应该更便宜,因为我们使用std::copy而不是在每次迭代重新索引:

#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::export]]
NumericVector combine(const List& list)
{
   std::size_t n = list.size();

   // Figure out the length of the output vector
   std::size_t total_length = 0;
   for (std::size_t i = 0; i < n; ++i)
      total_length += Rf_length(list[i]);

   // Allocate the vector
   NumericVector output = no_init(total_length);

   // Loop and fill
   std::size_t index = 0;
   for (std::size_t i = 0; i < n; ++i)
   {
      NumericVector el = list[i];
      std::copy(el.begin(), el.end(), output.begin() + index);

      // Update the index
      index += el.size();
   }

   return output;

}

/*** R
library(microbenchmark)
x <- replicate(10, runif(1E6), simplify = FALSE)
identical(unlist(x), combine(x))
microbenchmark(
   unlist(x),
   combine(x)
)
*/
Run Code Online (Sandbox Code Playgroud)

运行此代码可以让我:

> Rcpp::sourceCpp('C:/Users/Kevin/scratch/combine.cpp')

> library(microbenchmark)

> x <- replicate(10, runif(1E6), simplify = FALSE)

> identical(unlist(x), combine(x))
[1] TRUE

> microbenchmark(
+    unlist(x),
+    combine(x)
+ )
Unit: milliseconds
       expr      min       lq     mean   median       uq      max neval
  unlist(x) 21.89620 22.43381 29.20832 23.14454 35.32135 68.09562   100
 combine(x) 20.96225 21.55827 28.13269 22.08985 24.13403 51.68660   100
Run Code Online (Sandbox Code Playgroud)

所以,实际上是一样的.我们只是因为我们不进行任何类型检查而获得一点点时间(这意味着如果我们没有仅包含数字向量的列表,这个代码就会爆炸),但至少应该说明我们真的可以这里做得好多了.

(唯一的例外,我想,将使用大型向量,其中并行处理可能对此有帮助)