RcppArmadillo:内存使用问题
RcppArmadillo: Issue with memory usage
我已经开始使用 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_。所以这不应该使用任何额外的内存。然后当我初始化变量 disturb 时,我也使用指针并设置 copy_aux = FALSE。所以我不应该使用任何内存。因此,如果我的理解是正确的,那么当我 运行 代码时,我应该只使用 1.6 gb。这是正确的吗?
但是,当我 运行 代码时,内存使用量(通过查看 Ubuntu 中的系统监视器判断)跳到 10 gb 以上(从大约 1 gb),然后回落到大约4GB。我不明白这是怎么回事。我是否错误地使用了 Rcpp?
感谢您的帮助。非常感谢。
在新版 Armadillo (5.300) 之后编辑
在 Whosebug 上最初的 Q/A 之后,康拉德桑德森和我就这个问题进行了一些电子邮件讨论。按照设计,arma::cube
对象会为 cube
的每个切片(第三维)创建一个 arma::mat
。这是在创建 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)
这里的关键是现在使用 arma::cube disturb(input.begin(), 2, 2, 50000000, false, true, false);
调用 cube
构造函数。这里最后的 false
是新的 prealloc_mat
参数,它决定是否预分配矩阵。 slice
方法在没有预分配矩阵的 cube
上仍然可以正常工作——矩阵将按需分配。但是,如果您直接访问 cube
的 mat_ptrs
成员,它将被 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 数组,使最大维度不是第三维度。
EDIT 在做了更多挖掘之后,看起来好像 是 在创建 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" );
}
}
Cube_prealloc::mat_ptrs_size
好像是 4,所以对于任何超过 4 个切片的数组来说,这实际上都是一个问题。
我发布了一个issue on github。
EDIT2 但是,这绝对是底层犰狳代码的问题。这是一个完全不使用 Rcpp 的可重现示例。这仅是 linux - 它使用来自 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;
}
EDIT 3 根据上面的 create_mat
代码,为 each 切片创建了一个 arma::mat
立方体。在我的 64 位机器上,这会导致每个切片有 184 字节的开销。对于具有 5e7 个切片的多维数据集,这相当于 8.6 GiB 的开销,即使底层数字数据仅占用 1.5 GiB。我已经给 Conrad Sanderson 发了电子邮件,询问这是否是 Armadillo 工作方式的基础或是否可以更改,但现在看来您确实希望 slice
维度(第三个维度)成为如果可能的话,三个。还值得注意的是,这适用于 all cube
s,而不仅仅是那些从现有内存创建的。使用 arma::cube(dim1, dim2, dim3)
构造函数会导致相同的内存使用。
我已经开始使用 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_。所以这不应该使用任何额外的内存。然后当我初始化变量 disturb 时,我也使用指针并设置 copy_aux = FALSE。所以我不应该使用任何内存。因此,如果我的理解是正确的,那么当我 运行 代码时,我应该只使用 1.6 gb。这是正确的吗?
但是,当我 运行 代码时,内存使用量(通过查看 Ubuntu 中的系统监视器判断)跳到 10 gb 以上(从大约 1 gb),然后回落到大约4GB。我不明白这是怎么回事。我是否错误地使用了 Rcpp?
感谢您的帮助。非常感谢。
在新版 Armadillo (5.300) 之后编辑
在 Whosebug 上最初的 Q/A 之后,康拉德桑德森和我就这个问题进行了一些电子邮件讨论。按照设计,arma::cube
对象会为 cube
的每个切片(第三维)创建一个 arma::mat
。这是在创建 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)
这里的关键是现在使用 arma::cube disturb(input.begin(), 2, 2, 50000000, false, true, false);
调用 cube
构造函数。这里最后的 false
是新的 prealloc_mat
参数,它决定是否预分配矩阵。 slice
方法在没有预分配矩阵的 cube
上仍然可以正常工作——矩阵将按需分配。但是,如果您直接访问 cube
的 mat_ptrs
成员,它将被 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 数组,使最大维度不是第三维度。
EDIT 在做了更多挖掘之后,看起来好像 是 在创建 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" );
}
}
Cube_prealloc::mat_ptrs_size
好像是 4,所以对于任何超过 4 个切片的数组来说,这实际上都是一个问题。
我发布了一个issue on github。
EDIT2 但是,这绝对是底层犰狳代码的问题。这是一个完全不使用 Rcpp 的可重现示例。这仅是 linux - 它使用来自 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;
}
EDIT 3 根据上面的 create_mat
代码,为 each 切片创建了一个 arma::mat
立方体。在我的 64 位机器上,这会导致每个切片有 184 字节的开销。对于具有 5e7 个切片的多维数据集,这相当于 8.6 GiB 的开销,即使底层数字数据仅占用 1.5 GiB。我已经给 Conrad Sanderson 发了电子邮件,询问这是否是 Armadillo 工作方式的基础或是否可以更改,但现在看来您确实希望 slice
维度(第三个维度)成为如果可能的话,三个。还值得注意的是,这适用于 all cube
s,而不仅仅是那些从现有内存创建的。使用 arma::cube(dim1, dim2, dim3)
构造函数会导致相同的内存使用。