【问题标题】:Decreasing memory consumption in R -- pass by reference / data.table减少 R 中的内存消耗——通过引用/data.table 传递
【发布时间】:2016-07-20 15:03:44
【问题描述】:

通过将子集操作从基础 data.frame 操作移动到 data.table 操作,我已经实现了显着的加速(~6.5 倍)。但我想知道我是否可以在记忆力方面得到任何改善。

我的理解是 R 确实原生地通过引用传递(例如see here)。所以,我正在寻找一种方法(没有在Rcpp 中重写一个复杂的函数)来做到这一点。 data.table 提供了一些改进 [在编辑我的问题以包含以下 @joshua ulrich 发现的错字后]。但如果可能的话,我正在寻找更大的改进。

在我的实际用例中,我正在对大量数据集进行并行模拟,并通过模拟退火进行优化。由于开发时间增加和technical debt 增加,我宁愿不要在 Rcpp 中重新编写模拟退火和损失函数计算。

问题示例:

我主要关心的是从数据集中删除一些观察子集并添加另一个观察子集。这里给出了一个非常简单(荒谬)的例子。有没有办法减少内存使用?我当前的使用似乎是按值传递的,因此内存使用量 (RAM) 大约翻了一番。

library(data.table)
set.seed(444L)

df1 <- data.frame(matrix(rnorm(1e7), ncol= 10))
df2 <- data.table(matrix(rnorm(1e7), ncol= 10))

prof_func <- function(df) {
  s1 <- sample(1:nrow(df), size= 500, replace=F)
  s2 <- sample(1:nrow(df), size= 500, replace=F)
  return(rbind(df[-s1,], df[s2,]))
}

dt_m <- df_m <- vector("numeric", length= 500L)

for (i in 1:500) {

  Rprof("./DF_mem.out", memory.profiling = TRUE)
  y <- prof_func(df1)
  Rprof(NULL)
  df <- summaryRprof("./DF_mem.out", memory= "both")
  df_m[i] <- df$by.self$mem.total[which(rownames(df$by.self) == "\"rbind\"")]


  Rprof("./DT_mem.out", memory.profiling = TRUE)
  y2 <- prof_func(df2)
  Rprof(NULL)
  dt <- summaryRprof("./DT_mem.out", memory = "both")
  dt_m[i] <- dt$by.self$mem.total[which(rownames(dt$by.self) == "\"rbind\"")]

}
pryr::object_size(df1)
80 MB
pryr::object_size(df2)
80 MB

# EDITED: via typo / fix from @Joshua Ulrich.
# improvement in memory usage via DT. still not pass-by-reference
quantile(df_m, seq(0,1,.1))
    0%    10%    20%    30%    40%    50%    60%    70%    80%    90%   100% 
379.00 428.60 440.10 447.70 455.36 459.20 466.48 469.89 474.40 482.10 512.60 
quantile(dt_m, seq(0,1,.1))
    0%    10%    20%    30%    40%    50%    60%    70%    80%    90%   100% 
 76.80  84.50  84.50  92.10  92.10  92.10  92.10 107.30 116.46 130.20 157.00 

附录:

### speed improvement:
#-----------------------------------------------
library(data.table)
library(microbenchmark)

set.seed(444L)

df1 <- data.frame(matrix(rnorm(1e7), ncol= 10))
df2 <- data.table(matrix(rnorm(1e7), ncol= 10))

microbenchmark(
  df= {
    s1 <- sample(1:nrow(df1), size= 500, replace=F)
    s2 <- sample(1:nrow(df1), size= 500, replace=F)
    df1 <- rbind(df1[-s1,], df1[s2,])
  },
  dt= {
    s1 <- sample(1:nrow(df2), size= 500, replace=F)
    s2 <- sample(1:nrow(df2), size= 500, replace=F)
    df2 <- rbind(df2[-s1,], df2[s2,])

  }, times= 100L)

Unit: milliseconds
 expr      min        lq     mean   median       uq      max neval cld
   df 672.5106 757.65188 814.1582 809.6346 864.6668 998.2290   100   b
   dt  68.1254  85.73178 139.1256 120.3613 148.8243 397.7359   100  a 

【问题讨论】:

  • 我认为将示例调用包含在基准测试中没有多大意义,因为它们是共享的。对于rbind 的事情,我想您可以通过重新排列行号向量和子集一次而不是使用rbind 来节省速度。 myrows = df2[, c(.I[-s1], .I[s2])]; df2[myrows] 或类似的。顺便说一句,关于传递引用,你可能会感兴趣:stackoverflow.com/q/15759117/1191259 最后,如果你的真实应用程序像这样使用sample,你应该考虑切换到sample.int 或者至少写n 而不是1:n 作为第一个参数。
  • @Symbolix rbindlist 被方法调度自动调用...重新运行 Rprof 现在没有 Joshua Ulrich 在下面确定的错字
  • @Frank 感谢您的提示。
  • @Alex - 确实如此!谢谢指点。
  • 也许还可以参见 this 通过引用添加/删除行(或多或少)。不过有点复杂。

标签: r data.table pass-by-reference


【解决方案1】:

prof_func 有错误。它在 df1 上调用 rbind 而不是它的参数 (df)。修复该问题,您将看到 data.table 对象的内存使用减少。

library(data.table)
set.seed(444L)

df1 <- data.frame(matrix(rnorm(1e7), ncol= 10))
df2 <- data.table(matrix(rnorm(1e7), ncol= 10))

prof_func <- function(df) {
  s1 <- sample(1:nrow(df), size= 500, replace=F)
  s2 <- sample(1:nrow(df), size= 500, replace=F)
  return(rbind(df[-s1,], df[s2,]))
}

dt_m <- df_m <- vector("numeric", length= 500L)

for (i in 1:100) {
  Rprof("./DF_mem.out", memory.profiling = TRUE, interval=0.01)
  y <- prof_func(df1)
  Rprof(NULL)
  df <- summaryRprof("./DF_mem.out", memory= "both")
  df_m[i] <- df$by.total["\"rbind\"","mem.total"]

  Rprof("./DT_mem.out", memory.profiling = TRUE, interval=0.01)
  y2 <- prof_func(df2)
  Rprof(NULL)
  dt <- summaryRprof("./DT_mem.out", memory = "both")
  dt_m[i] <- dt$by.total["\"rbind\"","mem.total"]
}
quantile(df_m, seq(0,1,.1))
#    0%   10%   20%   30%   40%   50%   60%   70%   80%   90%  100% 
#   0.0   0.0   0.0   0.0   0.0   0.0   0.0 413.4 432.5 455.0 485.9 
quantile(dt_m, seq(0,1,.1))
#    0%   10%   20%   30%   40%   50%   60%   70%   80%   90%  100% 
#   0.0   0.0   0.0   0.0   0.0   0.0   0.0  53.9  84.5 122.6 153.1 

【讨论】:

  • 嗯,这是一个非常烦人的错字...谢谢。虽然我的真正问题没有错字,但如果可能的话,仍然寻找传递参考
  • @Alex:那么您需要重新编写示例以更好地说明您的实际问题,因为您的示例当前可以满足您的要求。
  • 内存开销来自计算,而不是垃圾回收后...如果您有任何想法,我很高兴在 cmets 中听到它们。
  • 是的,我同意——现在试着考虑一下……我会接受你对此的回答,看看我能否在单独的问题中提出一个更好的例子。
猜你喜欢
  • 2012-03-14
  • 1970-01-01
  • 2015-05-10
  • 2013-06-13
  • 1970-01-01
  • 2023-03-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多