在 R 中使用 sapply() 的内存高效方法



我正在尝试减少我一直在处理的一段 R 代码的内存消耗。我正在使用peakRAM()函数来测量使用的最大RAM。这是一个很长的代码,末尾有一个简单的sapply()函数。我发现这是消耗最大内存的sapply()部分。所以我写了一个小函数fun1()模仿我代码那部分的对象和sapply()函数,如下所示:

library(peakRAM)
fun1 <- function() {
tm <- matrix(1, nrow = 300, ncol = 10)  #in the original code, the entries are different and nonzero
print(object.size(tm))
r <- sapply(1:20000, function(i) {
colSums(tm[1:200,])  #in the original code, I am subsetting a 200 length vector which varies with i, stored in a list of length 20000
})
print(object.size(r))
r
}
peakRAM(fun1())

如果在 R 中运行它,您将获得大约 330Mb 的peakRAM()消耗。但是你可以看到tmr这两个对象都是非常小的(分别为2Kb和1.6Mb),如果你看一下计算单个colSums(tm[1:200,])peakRAM(),它非常小,比如0.1Mb。所以感觉,在sapply()期间,R在循环1:20000时可能没有摆脱内存。否则,由于单个colSums(tm[1:200,])占用的内存非常小,并且所有关联的对象都占用了小内存,因此sapply()占用的内存应该很小。

在这方面,我已经知道 R 有一个gc()函数,可以在需要时删除不必要的内存,并且可能 R 在sapply()期间没有清除内存,这导致了这种高内存消耗。如果这是真的,我想知道是否有办法摆脱这种情况并在不需要这么多额外内存的情况下完成工作?请注意,我不希望为此在运行时妥协。

这是您的函数,修改为使用vapply而不是sapply.colSums而不是colSums

f1 <- function(x, l) {
n <- ncol(x)
FUN <- function(i) .colSums(x[i, , drop = FALSE], length(i), n)
vapply(l, FUN, double(n), USE.NAMES = FALSE)
}

下面是一个 C 实现,R 可以通过inline包访问它:

sig <- c(x = "double", l = "list")
bod <- '
double *px = REAL(x);
R_xlen_t nx = XLENGTH(x);
int *d = INTEGER(getAttrib(x, R_DimSymbol));
int m = d[0];
int n = d[1];
R_xlen_t N = XLENGTH(l);
SEXP res = PROTECT(allocMatrix(REALSXP, n, N));
double *pres = REAL(res);
SEXP index;
R_xlen_t nindex;
int *pindex;
double sum;
for (R_xlen_t i = 0, rpos = 0; i < N; ++i)
{
index = VECTOR_ELT(l, i);
nindex = XLENGTH(index);
pindex = INTEGER(index);
for (R_xlen_t xpos = 0; xpos < nx; xpos += m, ++rpos)
{
sum = 0.0;
for (R_xlen_t k = 0; k < nindex; ++k)
{
sum += px[xpos + pindex[k] - 1];
}
pres[rpos] = sum;
}
}
UNPROTECT(1);
return res;
'
f2 <- inline::cfunction(sig, bod, language = "C")

这里的 C 代码非常少,所以我坚持使用 R API。您可以使用RcppAPI 编写等效的C++代码,您可能会发现这更平易近人。

下面是一个测试,显示f1f2给出相同的结果:

set.seed(1L)
m <- 300L
n <- 10L
x <- matrix(rnorm(m * n), m, n)
l <- replicate(2e+04, sample(m, size = 200L, replace = TRUE), simplify = FALSE)
identical(f1(x, l), f2(x, l))
## [1] TRUE

以下是在我的机器上分析f1(x, l)f2(x, l)的结果:

gc(FALSE)
Rprof("f.out", interval = 1e-05, memory.profiling = TRUE)
f1(x, l)
f2(x, l)
Rprof(NULL)
summaryRprof("f.out", memory = "both")[["by.total"]][c(""f1"", ""f2""), c("total.time", "mem.total")]
total.time mem.total
"f1"      0.119     344.4
"f2"      0.001       1.5

f1调用需要 0.119 秒,消耗 344.4 MiB 内存。f2调用需要 0.001 秒,消耗 1.5 MiB 的内存,这或多或少是返回值的大小。(请谨慎解释这些结果:Rprof附带许多警告。

最新更新