【问题标题】:How can I make update by reference faster than shallow copy?如何比浅拷贝更快地通过引用进行更新?
【发布时间】:2021-08-12 01:19:32
【问题描述】:

我创建了一些代码,我需要在大约 1M 行的循环中进行大量连接。起初我使用左连接制作浅拷贝,但我更改了代码以进行更新连接,它变得很慢。

一些测试数据

a=data.table(id=1:10^6)
b=data.table(id=1:10^6, t='y')

cols=setdiff(names(b),names(a))
c=copy(b[a,on=.(id)])
a[b,(cols):=mget(cols),on=.(id)]
all.equal(a,c)
[1]TRUE

我不知道这是否是比较时间的正确方法,但这里是

浅拷贝

microbenchmark(c=b[a,on=.(id)])
Unit: milliseconds
expr      min      lq     mean   median       uq      max neval
c 120.5911 122.734 128.1071 124.0922 126.1449 179.0127   100

更新 data.table

microbenchmark(a[b,(cols):=mget(cols),on=.(id)])
Unit: milliseconds
expr      min       lq     mean   median       uq      max
a[b, `:=`((cols), mget(cols)), on = .(id)] 159.6128 162.9798 175.9855 168.5807 184.8756 308.5406
neval
100

虽然我需要一个解决方案来更新多个列,但不知道为什么,但是使用 mget 比下面的方法慢

microbenchmark(a[b, t:=i.t, on=.(id)])
Unit: milliseconds
expr      min       lq     mean   median      uq      max neval
a[b, `:=`(t, i.t), on = .(id)] 138.0173 140.4671 146.3558 141.6204 143.281 185.1488   100

在这种情况下,我不关心内存使用,只关心速度。有没有比复制方法更快地更新原始表而不复制的方法?

【问题讨论】:

  • 不要使用 mget,因为它可能效率低下,请改用 env var
  • @jangorecki 我将研究环境,但你能提供一些如何使用它的例子吗?我在一个函数中使用它,其中colschange 中的名称基于用户提供的值。我现在对mget 有另一个疑问,不知道您是否可以帮助我,但是如果我在函数内部使用它并且在函数外部具有相同的对象名称,它将始终访问在函数内部创建的对象?我做了一些测试,发生了什么,但阅读帮助并不清楚。
  • @jangorecki,虽然我熟悉 envvars(..varname 表示上一级访问)并且新了解 wip env=list(...) 参数,但我无法获得类似 dfa[dfb, c(cols) := .(..cols), on = .(id)] 的信息正常工作。我猜这是一个我没有看到的微妙问题,但我认为如果您可以扩展您的第一条评论,这将非常有帮助。谢谢! (R.Cowboy,我将变量重命名为 dfadfb,因为我真的不喜欢在 R 中有一个名为 c 的对象,所以我将这三个都重命名。)
  • 另见stackoverflow.com/questions/60263274/…mget 很慢,因为内部代码需要为 .SD 变量分配内存。我将发布一个对用户不太友好的解决方案env = list(...)
  • env arg 只是使用基本 R 替代品的友好方式,@Cole 答案很棒!

标签: r data.table


【解决方案1】:

这是一种处理它的语言方式。基本上, 很棒,并且会尽最大努力进行优化。然而,像mget 这样的其他编程技巧会降低data.table 的效率,因为它必须假设j 表达式可以包含任何列,因此它需要分配内存来进行评估。

这会将mget 翻译成使用语言实际发生的事情:a[b, (cols) = list(t)]

nms = lapply(cols, as.name)
eval(substitute(a[b, (cols):= .x, on = .(id)], list(.x = as.call(c(quote(list), nms)))))

## performance against mget()

bench::mark(lang = {
  nms = lapply(cols, as.name)
  eval(substitute(a[b, (cols):= .x, on = .(id),], list(.x = as.call(c(quote(list), nms)))))
}
, mget_approach = a[b,(cols):=mget(cols),on=.(id)]
, normal =  b2[a2,on=.(id)]
  )

# A tibble: 3 x 13
  expression        min  median `itr/sec` mem_alloc `gc/sec` n_itr
  <bch:expr>    <bch:t> <bch:t>     <dbl> <bch:byt>    <dbl> <int>
1 lang            157ms   160ms      5.97      23MB     1.99     3
2 mget_approach   244ms   255ms      3.92    57.3MB     5.87     2
3 normal          162ms   174ms      5.82    34.4MB     3.88     3

## additional data to make above work: a2 = copy(a); b2 = copy(b)

这是检测到mget 的源代码, 需要考虑它。

https://github.com/Rdatatable/data.table/blob/94a12475f737892c542d3cb7daf42e534ea13a22/R/data.table.R#L1044-L1049

    # added 'mget' - fix for #994
    if (any(c("get", "mget") %chin% av)){
      if (verbose)
        catf("'(m)get' found in j. ansvars being set to all columns. Use .SDcols or a single j=eval(macro) instead. Both will detect the columns used which is important for efficiency.\nOld ansvars: %s \n", brackify(ansvars))
        # get('varname') is too difficult to detect which columns are used in general
        # eval(macro) column names are detected via the  if jsub[[1]]==eval switch earlier above.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-04-12
    • 2012-05-26
    • 2012-02-20
    • 1970-01-01
    • 1970-01-01
    • 2015-01-13
    • 2011-09-05
    相关资源
    最近更新 更多