RcppArmadillo:内存使用问题

时间:2015-07-15 19:15:56

标签: 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中的变量 input 时所以我应该只使用大约1.6 gb(2 * 2 * 50) * 8 = 1600)。当我转到Rcpp时,我使用SEXP对象初始化变量 input _ ,这是一个指针。所以这不应该使用任何额外的内存。然后,当我初始化变量打扰时,我也使用指针并设置copy_aux = FALSE。所以我不应该使用任何记忆。因此,如果我的理解是正确的,那么在运行代码时我应该只使用1.6 GB。这是对的吗?

然而,当我运行代码时,内存使用情况(通过查看Ubuntu中的系统监视器判断)跳到10 gb以上(从大约1 gb),然后再下降到4 gb左右。我不明白发生了什么。我是否错误地使用了Rcpp?

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

1 个答案:

答案 0 :(得分:4)

在新版本的Armadillo(5.300)之后编辑

在StackOverflow上的这个初始Q / A之后,Conrad Sanderson和我就这个问题进行了一些电子邮件讨论。根据设计,arma::cube个对象为arma::mat的每个切片(第三维)创建cube。这是在创建cube期间完成的,即使数据是从现有内存中复制的(如原始问题中所示)。由于并不总是需要,我建议应该有一个选项来禁用切片的矩阵预分配。截至当前版本的犰狳(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的{​​{1}}成员,则会填充cube个指针。 The help has also been updated.

非常感谢Conrad Sanderson如此迅速地提供这个额外的选择,感谢Dirk Eddelbuettel在Rcpp和RcppArmadillo的所有工作!

原始回答

这有点奇怪。我尝试过一系列不同的数组大小,问题只发生在第三维比另一维大得多的数组中。这是一个可重现的例子:

NULL

这表明它是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)。这肯定不是你做错了。

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

目前,我建议如果可能的话,组织你的3d数组,使最大的维度不是第三个。

编辑在进行了一些挖掘之后,在创建RcppArmadillo期间,似乎 分配了RAM。在arma::cube中,在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" ); } } 似乎是4,所以对于任何超过4个切片的数组来说,这实际上都是一个问题。

我发布了issue on github

EDIT2 但是,这绝对是潜在的Armadillo代码的问题。这是一个可重复的例子,根本不使用Rcpp。这是仅限linux的 - 它使用How to get memory usage at run time in c++?中的代码来提取正在运行的进程的当前内存使用情况。

Cube_prealloc::mat_ptrs_size

编辑3 根据上面的#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; } 代码,为多维数据集的每个切片创建create_mat。在我的64位机器上,每个切片产生184字节的开销。对于具有5e7切片的多维数据集,即使基础数字数据仅占用1.5 GiB,也相当于8.6 GiB的开销。我已经通过电子邮件向Conrad Sanderson发送电子邮件,询问这是否是Armadillo工作方式或可以改变方式的基础,但是现在看来你肯定希望你的arma::mat尺寸(第三个)尺寸最小。三,如果可能的话。值得注意的是,这适用于所有 slice,而不仅仅是从现有内存创建的那些。使用cube构造函数会导致相同的内存使用。