r - 高效合并大型 data.tables

标签 r performance memory merge data.table

我有两个相当大的 data.table 对象要合并。

  • dt1 在 5 列上有 500.000.000 个观察值。
  • dt2 在 2 列上有 300.000 个观察值。

两个对象都有相同的key,称为id

我想 left_join 信息从 dt2dt1

例如:

dt1  <- data.table(id = c(1, 2, 3, 4),
               x1 = c(12, 13, 14, 15),
               x2 = c(5, 6, 7, 8),
               x3 = c(33, 44, 55, 66),
               x4 = c(123, 123, 123, 123))

dt2 <- data.table(id = c(1, 2, 3, 4),
              x5 = c(555, 666, 777, 888))
setkey(dt1, id)
setkey(dt2, id)

dt2[dt1, on="id"] 

> dt2[dt1, on="id"]
   id  x5 x1 x2 x3  x4
1:  1 555 12  5 33 123
2:  2 666 13  6 44 123
3:  3 777 14  7 55 123
4:  4 888 15  8 66 123

但是,当合并我的原始数据时,R 不能再分配内存了。然而,合并的输出适合 RAM。

完成这种大型合并的最有效(速度与内存限制)方法是什么?

我们应该拆分-应用-合并吗?

我们应该使用数据库库来完成这项工作吗?

您将如何有效地做到这一点?

最佳答案

键控分配应该节省内存。

dt1[dt2, on = "id", x5 := x5]

Should we use a DB library to get this done?

这可能是个好主意。如果设置和使用数据库对您来说很痛苦,请尝试 RSQLite 包裹。这很简单。


我的实验

tl;dr:与合并和替换相比,键控分配使用的内存减少了 55%,举个玩具例子。

我写了两个脚本,每个脚本都有一个设置脚本,dt-setup.R创建 dt1dt2 .第一个脚本,dt-merge.R , 更新 dt1通过“合并”方法。第二个,dt-keyed-assign.R , 使用键控分配。两个脚本都使用 Rprofmem() 记录内存分配。功能。

为了不折磨我的笔记本电脑,我有 dt1为 500,000 行和 dt2 3,000 行。

脚本:

# dt-setup.R
library(data.table)

set.seed(9474)
id_space <- seq_len(3000)
dt1  <- data.table(
  id = sample(id_space, 500000, replace = TRUE),
  x1 = runif(500000),
  x2 = runif(500000),
  x3 = runif(500000),
  x4 = runif(500000)
)
dt2 <- data.table(
  id = id_space,
  x5 = 11 * id_space
)
setkey(dt1, id)
setkey(dt2, id)
# dt-merge.R
source("dt-setup.R")
Rprofmem(filename = "dt-merge.out")
dt1 <- dt2[dt1, on = "id"]
Rprofmem(NULL)
# dt-keyed-assign.R
source("dt-setup.R")
Rprofmem(filename = "dt-keyed-assign.out")
dt1[dt2, on = "id", x5 := x5]
Rprofmem(NULL)

在我的工作目录中使用所有三个脚本,我在单独的 R 进程中运行每个加入脚本。

system2("Rscript", "dt-merge.R")
system2("Rscript", "dt-keyed-assign.R")

我认为输出文件中的行通常遵循模式 "<bytes> :<call stack>" .我还没有找到很好的文档。但是,前面的数字从未低于 128,这是 R 不低于的默认最小字节数 malloc用于向量。

请注意,并非所有这些分配都会添加到 R 使用的 total 内存中。 R 可能会在垃圾回收后重用它已经拥有的一些内存。因此,这不是衡量在任何特定时间使用了多少内存的好方法。但是,如果我们假设垃圾收集行为是独立的,它确实可以作为脚本之间的比较。

内存报告的一些示例行:

cat(readLines("dt-merge.out", 5), sep = "\n")
# 90208 :"get" "[" 
# 528448 :"get" "[" 
# 528448 :"get" "[" 
# 1072 :"get" "[" 
# 20608 :"get" "["

还有像 new page:"get" "[" 这样的行用于页面分配。

幸运的是,这些很容易解析。

parse_memory_report <- function(path) {
  report <- readLines(path)
  new_pages <- startsWith(report, "new page:")
  allocations <- as.numeric(gsub(":.*", "", report[!new_pages]))
  total_malloced <- sum(as.numeric(allocations))
  message(
    "Summary of ", path, ":\n",
    sum(new_pages), " new pages allocated\n",
    sum(as.numeric(allocations)), " bytes malloced"
  )
}

parse_memory_report("dt-merge.out")
# Summary of dt-merge.out:
# 12 new pages allocated
# 32098912 bytes malloced

parse_memory_report("dt-keyed-assign.out")
# Summary of dt-keyed-assign.out:
# 13 new pages allocated
# 14284272 bytes malloced

重复实验时,我得到了完全相同的结果。

所以键控分配有一个多页分配。页面的默认字节大小是 2000。我不知道 malloc有效,并且 2000 相对于所有分配来说很小,所以我将忽略这种差异。如果这是愚蠢的,请责备我。

因此,忽略页面,键控分配分配的内存比合并少 55%。

关于r - 高效合并大型 data.tables,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56013991/

相关文章:

r - 如何根据R中列值的范围拆分数据帧?

r - R中最后一个逗号分割的字符串

javascript - 如何加速 javascript 表格渲染?

c++ - 查看内存窗口中的数据

r - 从 R 中的第一个、第二个和第三个括号中提取文本

r - 在 R 中选择没有缺失值的行

java - Android/Java 网络IO慢

javascript - 延迟 Javascript 解析给出错误

c - 如果 union 的格式不同,C 如何解释来自 union 的数据?

linux - fmem 编译错误与 make