Edit после новой версии Armadillo (5.300)
После этого начального Q/A на StackOverflow, Конрад 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
cube
, он будет заполнен указателями NULL
. The help has also been updated.
Огромное спасибо Конраду Сандерсону за то, что он так быстро выполнил эту дополнительную функцию, а Дирк Эддельбуэттель за всю свою работу над Rcpp и RcppArmadillo!
Оригинальный ответ
Это немного странно один. Я попытался с диапазоном различных размеров массивов, а проблема возникает только с массивами, где третье измерение намного больше, чем другой 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 Однако, это определенно проблема с базовым кодом 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;
}
EDIT 3 согласно create_mat
кода выше, arma::mat
создается для каждого среза куба. На моей 64-разрядной машине это приводит к 184 байтам служебных данных для каждого фрагмента. Для куба с 5e7 срезами, равным 8,6 гигабайт накладных расходов, даже если базовые числовые данные занимают всего 1,5 гигабайта. Я отправил по электронной почте Конраду Сандерсону, чтобы узнать, является ли это основополагающим для того, как Армадилло работает или может быть изменен, но на данный момент определенно кажется, что вы хотите, чтобы ваш размер (третий) был самым маленьким из трех, если вообще возможное. Также стоит отметить, что это относится к всемcube
s, а не только к тем, которые созданы из существующей памяти. Использование конструктора arma::cube(dim1, dim2, dim3)
приводит к тому же использованию памяти.
Я тестировал ту же проблему, используя ** sourceCpp() ** и как R-пакет. У меня такая же проблема с большим непредвиденным использованием памяти, когда я передаю большие объекты на C++. –
Это, похоже, не проблема для 'arma :: mat', но я получаю точно такую же проблему с' arma :: cube'. Может ли это относиться к отсутствию надлежащего класса 3d-массива внутри Rcpp? –
Да. Он работает для arma :: mat. Но так как ** input _ ** является Rcpp :: NumericVector, и я использую указатель на инициализацию переменной arma :: cube, должно ли это иметь значение? Я, вероятно, ошибаюсь. –