为何data.table两种连接方式中mget版本内存占用翻倍?
data.table中mget连接赋值耗时/内存翻倍的原因与优化
问题描述
我一直使用data.table的x[y, on = .(join), col := i.col]格式进行数据连接。当需要从表y中连接大量列时,使用mget可省去手动输入所有列的繁琐,十分便捷。但我发现,使用mget的方式比手动列出y中列的方式,内存占用和耗时都翻倍了。这说明某处发生了复制操作,但我不确定具体位置和原因。想了解使用mget为何会导致额外的资源消耗。
复现代码
library(data.table) library(profvis) set.seed(42) # 生成测试数据 data1 <- function() { dt_1 <- as.data.table(matrix(rnorm(1e7 * 5), ncol = 5)) dt_1[, join := sample.int(1e5, 1e7, replace = TRUE)] return(dt_1) } data2 <- function() { dt_2 <- as.data.table(matrix(rnorm(1e5 * 5), ncol = 5)) dt_2[, join := sample.int(1e5, 1e5)] setnames(dt_2, new = c("X1", "X2", "X3", "X4", "X5", "join")) return(dt_2) } # 使用mget的测试 dt_1 <- data1() dt_2 <- data2() profvis({ dt_1[dt_2, on = .(join), (names(dt_2)) := mget(paste0("i.", names(dt_2)))] }) # 手动列名的测试 dt_1 <- data1() dt_2 <- data2() profvis({ dt_1[dt_2, on = .(join), `:=`(X1 = i.X1, X2 = i.X2, X3 = i.X3, X4 = i.X4, X5 = i.X5)] })
核心原因:mget触发了额外的数据复制
当你手动列出i.X1、i.X2这类列时,data.table会直接引用y表(即dt_2)中对应列的内存地址,在x表(dt_1)的对应行上原地修改,几乎没有额外的内存开销。
但使用mget(paste0("i.", names(dt_2)))时,会发生两个关键的额外操作:
- 创建临时列表副本:
mget会将所有i.*列提取出来,创建一个包含这些列的新列表。这个列表是dt_2列的完整副本,直接导致内存占用瞬间翻倍(原dt_2的列还在内存中,加上新的副本)。 - 无法利用原地修改优化:
data.table在处理单个i.col赋值时,可以直接映射内存地址完成修改;但处理mget返回的列表时,需要先将列表中的每一列复制到dt_1中,这中间的复制过程会增加耗时,并且进一步占用内存。
优化方案:循环赋值替代mget
如果需要批量处理大量列,又不想手动输入所有列名,可以用循环结合get来实现,这种方式的效率和手动列名几乎一致:
dt_1 <- data1() dt_2 <- data2() # 排除连接键,只处理需要复制的列 cols_to_copy <- setdiff(names(dt_2), "join") profvis({ for (col in cols_to_copy) { dt_1[dt_2, on = .(join), (col) := get(paste0("i.", col))] } })
这个循环中,每次只处理一列,data.table依然能利用原地修改的优化,不会产生额外的列表副本,内存占用和耗时都会和手动列名的方式接近。
内容的提问来源于stack exchange,提问作者Aaron
相关产品推荐
相关产品推荐

