内存高效的scale()函数

问题描述 投票:0回答:2

我正在尝试缩放一个大矩阵(我实际使用的矩阵要大得多):

x <- matrix(rnorm(1e8), nrow=1e4)
x <- scale(x)

该矩阵使用约 800 MB 内存。然而,在 lineprof 中,我看到

scale()
函数分配了 9.5 GB 内存,并在运行完成后释放了 8.75 GB 内存。因为这个函数的内存效率很低,所以当我运行它时,它有时会崩溃我的会话。

我正在尝试找到一种节省内存的方法来运行此函数。如果我自己编码,它只分配 ~6.8 GB,但这看起来仍然很多:

x <- matrix(rnorm(1e8), nrow=1e4)
u <- apply(x, 2, mean)
s <- apply(x, 2, sd)
x <- t((t(x) - u)/s)

我认为我可以通过将 x 的列分成组,然后分别缩放每个列组来做得更好:

x <- matrix(rnorm(1e8), nrow=1e4)
g <- split(1:ncol(x), ceiling(1:ncol(x)/100))
for (j in g) {
  x[, j] <- scale(x[, j])
}

使用 profvis,我发现该功能总体效率较低。它分配 10.8 GB 内存并释放 10.5 GB。然而,我认为 R 可能可以在

for
循环内进行垃圾收集,但它没有这样做,因为它不需要这样做。它是否正确?如果是这样,那么这可能是最好的选择?


问题:

• 编写此类函数以避免内存崩溃的最佳方法是什么?(如果有可用的软件包,那就更好了)

• 在分析代码时如何考虑垃圾回收? 我的理解是,除非需要,否则 GC 并不总是运行。


更新:就运行时间而言,将列分成 10 组并不比使用

scale(x)
函数慢多少。在 [1000 x 1000] 矩阵上运行这两个函数,使用
microbenchmark
评估的平均运行时间为:

• 

scale(x)
= 154 毫秒

• 分成 10 个列组 = 167 毫秒

• 分成 1000 个列组(即分别缩放每列)= 373 毫秒

r memory memory-management
2个回答
1
投票

鉴于您不需要保留原始矩阵,您可以通过直接修改它(而不是复制它)来节省一些内存。此外,您可以使用简单的 for 循环绕过 base::scale。例如:

library(profvis) # to profile RAM usage and time
library(matrixStats) # CPU/RAM efficient versions of rowMeans

profvis({
  set.seed(1)
  x = matrix(rnorm(1e7), nrow=1e3) # Note that I reduced nrow and ncol (I do not have enough RAM to test at your desired matrix dimension)
  x = scale(x)
})

profvis({
  set.seed(1)
  x = matrix(rnorm(1e7), nrow=1e3) # Note that I reduced nrow and ncol (I do not have enough RAM to test at your desired matrix dimension)
  mu = matrixStats::colMeans2(x)
  sigma = matrixStats::colSds(x)
  for(i in 1:ncol(x))
  {
    x[,i] = (x[,i]-mu[i])/sigma[i]
  }
})

在我的机器中,仅通过这些微小的变化,峰值内存就会大幅减少(请在您所需的矩阵尺寸上进行测试)。


-1
投票

修改我的答案,感谢

adn bps
关于内存使用的评论。首先我使用
gc{base}
垃圾收集功能,来释放一些内存。

gc()
          used (Mb) gc trigger (Mb) max used (Mb)
Ncells  684317 36.6    1168576 62.5   940480 50.3
Vcells 1053307  8.1    2060183 15.8  1359327 10.4
gc(reset = TRUE)
          used (Mb) gc trigger (Mb) max used (Mb)
Ncells  684296 36.6    1168576 62.5   684296 36.6
Vcells 1053271  8.1    2060183 15.8  1053271  8.1

我找到了一个我认为可以帮助你的表格,首先我用Rcpp包生成rnorm矩阵,使用c ++代码,它加快了过程一点

library(Rcpp)
cppFunction('NumericVector ranM(int n, int m) { 
   NumericVector v = rnorm(n * m);
   v.attr("dim") = Dimension(n, m);
   return v; 
}')
 system.time(x <- ranM(1e4,1e4))
   user  system elapsed 
   7.19    0.09    7.30 
 system.time(y<- matrix(rnorm(1e8), nrow=1e4))
   user  system elapsed 
  10.67    0.42   11.09

矩阵

x
y
的大小相同

print(object.size(x), units = "auto")
762.9 Mb
print(object.size(y), units = "auto")
762.9 Mb
#system.time(w <- scale(x))
#   user  system elapsed 
#  11.86    5.79  221.54 without using gc(TRUE)
system.time(w <- scale(x))
   user  system elapsed 
   9.52    5.39   47.33 using gc(TRUE)

remove(w,y)

我加载库

data.table
,并将矩阵 x 转换为 data.table 类,以使用缩放函数

library(data.table)
system.time(z <- data.table(x))
system.time(z <- data.table(x))
   user  system elapsed 
   1.18    0.33    1.55 
system.time(z<-z[, lapply(.SD, scale)])
   user  system elapsed 
   8.34    0.21    8.58 
print(object.size(z), units = "auto")
763.5 Mb

现在我使用bigmemory库来有效利用内存,如果需要的话我会删除原始矩阵x,以免环境中积累重物

library(bigmemory)
system.time(z <- as.big.matrix(z))
   user  system elapsed 
  15.90    6.64   23.34
print(object.size(x), units = "b")/print(object.size(z), units = "auto")
800000200 bytes
664 bytes
1204819.6 bytes
remove(x)
gc()
          used (Mb) gc trigger   (Mb)  max used   (Mb)
Ncells  783279 41.9    1442291   77.1   1442291   77.1
Vcells 1180947  9.1  461812104 3523.4 601095521 4586.0

经过的时间显示出相当大的时间改进,大约快了 5 倍。 请注意,bigmatrix 对象小了一百万倍。您可以重现一个简短的示例,以查看矩阵和 bib.matrix 中比例的结果是相等的

set.seed(1)
m1 <- matrix(rnorm(5*5), nrow = 5)
m2 <- as.big.matrix(m1)
class(m2)
[1] "big.matrix"
attr(,"package")
[1] "bigmemory"
scale(m1) == scale(m2[,])
     [,1] [,2] [,3] [,4] [,5]
[1,] TRUE TRUE TRUE TRUE TRUE
[2,] TRUE TRUE TRUE TRUE TRUE
[3,] TRUE TRUE TRUE TRUE TRUE
[4,] TRUE TRUE TRUE TRUE TRUE
[5,] TRUE TRUE TRUE TRUE TRUE
© www.soinside.com 2019 - 2024. All rights reserved.