You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 05:25:30