c++ - Rcpp Armadillo : Issue with memory usage

标签 c++ r memory rcpp

我已经开始使用 Rcpp。我很喜欢。我对编程相当陌生。我有一个关于内存使用的问题。下面是一个可重现的问题:

library(RcppArmadillo)
library(inline)
code <- "
  Rcpp::NumericVector input_(input);
  arma::cube disturb(input_.begin(), 2, 2, 50000000, false);
  return wrap(2);
"
Test <- cxxfunction(signature(input = "numeric"), plugin = "RcppArmadillo", body = code)
input <- array(rnorm(2 * 2 * 50000000), dim = c(2, 2, 50000000))
Test(input)

我的理解是,在上面的问题中,唯一的内存使用是当我将数组分配给变量 时。输入 在 R. 所以我应该只使用大约 1.6 gb (2*2*50*8 = 1600)。当我去 Rcpp 时,我初始化了变量 输入_ 使用作为指针的 SEXP 对象。所以这不应该使用任何额外的内存。然后当我初始化变量 打扰 ,我也使用一个指针并设置 copy_aux = FALSE。所以我不应该使用任何内存。因此,如果我的理解是正确的,那么我在运行代码时应该只使用 1.6 GB。这样对吗?

但是,当我运行代码时,内存使用量(通过查看 Ubuntu 中的系统监视器来判断)在回落到 4 GB 之前跃升至 10 GB 以上(从大约 1 GB)。我不明白发生了什么。我是否错误地使用了 Rcpp?

感谢您的帮助。非常感谢。

最佳答案

新版 Armadillo (5.300)后编辑

在 StackOverflow 上的这个初步问答之后,Conrad Sanderson 和我就这个问题进行了一些电子邮件讨论。按照设计,arma::cube对象创建 arma::mat对于 cube 的每个切片(第三维) .这是在创建 cube 期间完成的。 ,即使数据是从现有内存复制的(如原始问题)。由于这并不总是需要,我建议应该有一个选项来禁用切片的矩阵预分配。从当前版本的 Armadillo (5.300.4) 开始,现在有了。这可以从 CRAN 安装。

示例代码:

library(RcppArmadillo)
library(inline)
code <- "
  Rcpp::NumericVector input_(input);
  arma::cube disturb(input_.begin(), 2, 2, 50000000, false, true, false);
  return wrap(2);
"
Test <- cxxfunction(signature(input = "numeric"), plugin = "RcppArmadillo", body = code)
input <- array(rnorm(2 * 2 * 50000000), dim = c(2, 2, 50000000))
Test(input)

这里的关键是cube现在使用 arma::cube disturb(input.begin(), 2, 2, 50000000, false, true, false); 调用构造函数.最后false这是新的 prealloc_mat参数决定是否预先分配矩阵。 slice方法在 cube 上仍然可以正常工作没有预先分配的矩阵 - 矩阵将按需分配。但是,如果您直接访问 mat_ptrs成员(member)cube它将充满 NULL指针。 The help has also been updated.

非常感谢 Conrad Sanderson 如此迅速地提供这个附加选项,并感谢 Dirk Eddelbuettel 在 Rcpp 和 RcppArmadillo 上所做的所有工作!

原答案

这是一个有点奇怪的。我尝试了一系列不同的数组大小,问题只发生在第 3 维比其他 2 维大得多的数组中。这是一个可重现的例子:
library("RcppArmadillo")
library("inline")
code <- "
Rcpp::NumericVector input_(input);
IntegerVector dim = input_.attr(\"dim\");
arma::cube disturb(input_.begin(), dim[0], dim[1], dim[2], false);
disturb[0, 0, 0] = 45;
return wrap(2);
"
Test <- cxxfunction(signature(input = "numeric"), plugin = "RcppArmadillo", body = code)
input <- array(0, c(1e7, 2, 2))
Test(input)
# no change in memory usage

dim(input) <- c(2, 1e7, 2)
gc()
Test(input)
# no change in memory usage

dim(input) <- c(2, 2, 1e7)
gc()
Test(input)
# spike in memory usage

dim(input) <- c(20, 2, 1e6)
gc()
Test(input)
# no change in memory usage

这表明它与 Aramadillo 的方式有关。库已实现(或可能是 RcppArmadillo )。这当然似乎不是你做错了什么。

注意我已经包含了一些修改来代替数据(将第一个元素设置为 45),您可以确认在每种情况下数据 就地修改,表明没有复制正在进行。

现在,我建议如果可能的话,组织你的 3d 数组,这样最大的维度不是第三个维度。

编辑 再挖了几下,好像还有在创建 arma::cube 期间分配 RAM .在 Cube_meat.hpp ,在 create_mat方法,有如下代码:

if(n_slices <= Cube_prealloc::mat_ptrs_size)
  {
  access::rw(mat_ptrs) = const_cast< const Mat<eT>** >(mat_ptrs_local);
  }
else
  {
  access::rw(mat_ptrs) = new(std::nothrow) const Mat<eT>*[n_slices];

  arma_check_bad_alloc( (mat_ptrs == 0), "Cube::create_mat(): out of memory" );
  }
}
Cube_prealloc::mat_ptrs_size似乎是 4,所以对于任何超过 4 个切片的数组来说,这实际上是一个问题。

我发了一个 issue on github .

编辑2 但是,这绝对是底层 Armadillo 代码的问题。这是一个完全不使用 Rcpp 的可重现示例。这是 linux-only - 它使用来自 How to get memory usage at run time in c++? 的代码拉出正在运行的进程的当前内存使用情况。
#include <iostream>
#include <armadillo>
#include <unistd.h>
#include <ios>
#include <fstream>
#include <string>

//////////////////////////////////////////////////////////////////////////////
//
// process_mem_usage(double &, double &) - takes two doubles by reference,
// attempts to read the system-dependent data for a process' virtual memory
// size and resident set size, and return the results in KB.
//
// On failure, returns 0.0, 0.0

void process_mem_usage(double& vm_usage, double& resident_set)
{
   using std::ios_base;
   using std::ifstream;
   using std::string;

   vm_usage     = 0.0;
   resident_set = 0.0;

   // 'file' stat seems to give the most reliable results
   //
   ifstream stat_stream("/proc/self/stat",ios_base::in);

   // dummy vars for leading entries in stat that we don't care about
   //
   string pid, comm, state, ppid, pgrp, session, tty_nr;
   string tpgid, flags, minflt, cminflt, majflt, cmajflt;
   string utime, stime, cutime, cstime, priority, nice;
   string O, itrealvalue, starttime;

   // the two fields we want
   //
   unsigned long vsize;
   long rss;

   stat_stream >> pid >> comm >> state >> ppid >> pgrp >> session >> tty_nr
               >> tpgid >> flags >> minflt >> cminflt >> majflt >> cmajflt
               >> utime >> stime >> cutime >> cstime >> priority >> nice
               >> O >> itrealvalue >> starttime >> vsize >> rss; // don't care about the rest

   stat_stream.close();

   long page_size_kb = sysconf(_SC_PAGE_SIZE) / 1024; // in case x86-64 is configured to use 2MB pages
   vm_usage     = vsize / 1024.0;
   resident_set = rss * page_size_kb;
}

using namespace std;
using namespace arma;

void test_cube(double* numvec, int dim1, int dim2, int dim3) {
  double vm, rss;

  cout << "Press enter to continue";
  cin.get();

  process_mem_usage(vm, rss);
  cout << "Before:- VM: " << vm << "; RSS: " << rss << endl;

  cout << "cube c1(numvec, " << dim1 << ", " << dim2 << ", " << dim3 << ", false)" << endl;
  cube c1(numvec, dim1, dim2, dim3, false);

  process_mem_usage(vm, rss);
  cout << "After:-  VM: " << vm << "; RSS: " << rss << endl << endl;
}

int
main(int argc, char** argv)
  {
  double* numvec = new double[40000000];

  test_cube(numvec, 10000000, 2, 2);
  test_cube(numvec, 2, 10000000, 2);
  test_cube(numvec, 2, 2, 1000000);
  test_cube(numvec, 2, 2, 2000000);
  test_cube(numvec, 4, 2, 2000000);
  test_cube(numvec, 2, 4, 2000000);
  test_cube(numvec, 4, 4, 2000000);
  test_cube(numvec, 2, 2, 10000000);

  cout << "Press enter to finish";
  cin.get();

  return 0;
  }

编辑 3 create_mat上面的代码,一个 arma::mat是为 创建的每个切片的立方体。在我的 64 位机器上,这会导致每个切片产生 184 字节的开销。对于具有 5e7 个切片的多维数据集,这相当于 8.6 GiB 的开销,即使基础数字数据仅占用 1.5 GiB。我已经给康拉德·桑德森发了电子邮件,询问这是否是 Armadillo 工作方式的基础或可以改变,但现在看来你肯定想要你的 slice如果可能的话,维度(第三个)是三个中最小的一个。还值得注意的是,这适用于 全部 cube s,而不仅仅是那些从现有内存中创建的。使用 arma::cube(dim1, dim2, dim3)构造函数导致相同的内存使用。

关于c++ - Rcpp Armadillo : Issue with memory usage,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31439134/

相关文章:

r - 我们可以使用哪些方法来 reshape 非常大的数据集?

c++ - 使用 impi 库删除未分配内存时的错误

python - 输入到 C++ 可执行 python 子进程

c++ - 在 Qt 中使用将 pdf 图像转换为 jpg 图像

r - 在 R 中生成大量组合

c++ - 为什么memcpy复制Eigen矩阵数据失败,std::copy成功?

c# - 使用 File.Encrypt 加密文件,然后将其解密到内存流

c++ - 在 C++ 中的另一个类的方法中使用实例的变量

c++ - 用于获取范围的第 n 个元素的 Boost.Range 算法

r - ggplot2 中 X 轴变量标签上的真正减号