【问题标题】:Memory efficient scale() function节省内存的 scale() 函数
【发布时间】:2018-09-22 19:47:58
【问题描述】:

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

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] 矩阵上运行这两个函数,使用微基准评估的平均运行时间为:

• 比例 (x) = 154 毫秒

• 分成 10 个列组 = 167 毫秒

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

【问题讨论】:

  • 我可能会查看 matrixStats 包以计算列均值和 SD,然后使用这些手动进行。我还想知道是否直接使用列均值/标准差创建带有 byrow = TRUE 的矩阵,然后进行减法/除法可能比所有转置更快。

标签: r memory memory-management


【解决方案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

矩阵xy的大小是一样的

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 类,以使用 scale 函数

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 对象小于 100 万倍。一个简短的示例,您可以重现以查看 matrix 和 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

【讨论】:

  • 不错的解决方案!我最关心的是 peak 内存使用情况。您的解决方案很快,但我认为峰值内存使用率仍然很高,因为它在整个对象上使用了“缩放”功能。
猜你喜欢
  • 2013-04-03
  • 2017-11-20
  • 1970-01-01
  • 1970-01-01
  • 2013-05-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多