R语言 内存高效scale()函数

vuktfyat  于 2023-01-10  发布在  其他
关注(0)|答案(2)|浏览(194)

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

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 ms
·拆分为1000个列组(即分别缩放每个列)= 373 ms

toe95027

toe950271#

假设你不需要保留原始矩阵,你可以直接修改它(而不是复制它)来保存一些内存。另外,你可以用一个简单的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]
  }
})

在我的机器中,只有这些微小的改变才能显著降低峰值内存(请在您想要的矩阵维度上测试)。

zi8p0yeb

zi8p0yeb2#

根据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对象小了1百万倍。您可以重现一个简短示例,以查看matrix和bib.matrix中的scale结果相等

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

相关问题