键赋值应该节省内存。
dt1[dt2, on = "id", x5 := x5]
我们应该使用一个数据库库来完成这个任务吗?
这可能是个不错的主意。如果设置和使用数据库对你来说很痛苦,可以尝试使用RSQLite
包。它非常简单。
我的实验
简短概述:针对一个玩具示例,键入赋值比合并替换少使用55%的内存。
我编写了两个脚本,每个脚本都调用一个设置脚本dt-setup.R
来创建dt1
和dt2
。第一个脚本dt-merge.R
通过“合并”方法更新dt1
。第二个脚本dt-keyed-assign.R
则使用键入赋值。两个脚本都使用Rprofmem()
函数记录内存分配情况。
为了不让我的笔记本电脑受折磨,我设置了dt1
为500,000行,dt2
为3,000行。
脚本:
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使用的总内存。 R可以在垃圾回收后重用一些已有的内存。因此,这不是衡量任何特定时间使用多少内存的好方法。但是,如果我们假设垃圾回收行为是独立的,则它确实可以作为脚本之间比较的一种方法。
内存报告的一些示例行:
cat(readLines("dt-merge.out", 5), sep = "\n")
还有一些像 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")
parse_memory_report("dt-keyed-assign.out")
重复实验时,我得到了完全相同的结果。
因此,键分配多了一个页面分配。页面的默认字节大小为2000。我不确定malloc
是如何工作的,而且相对于所有分配来说,2000微不足道,所以我会忽略这种差异。如果这样做很蠢,请批评我。
因此,不考虑页面,键分配分配的内存比合并少了55%。