Почему: = быстрее, чем ': ='()?

Обычно я использую функциональную форму `:=`() для вычисления нескольких столбцов в data.table, думая, что это самый эффективный метод. Но недавно я обнаружил, что это медленнее, чем просто многократное использование :=. По крайней мере, на моем компьютере.

Я предполагаю, что могут быть некоторые накладные расходы с функциональной формой := но это вся причина, почему это медленнее? Я просто из любопытства спрашиваю, чтобы лучше понять внутренности data.table.

library(data.table)


n <- 5000000
dt <- data.table(a = rnorm(n),
                 b = rnorm(n),
                 c = rnorm(n))

dt_a <- copy(dt)

system.time({
  dt_a[, d := a + b]
  dt_a[, e := b + c]
  dt_a[, f := a + c]
})
#>    user  system elapsed 
#>   0.076   0.060   0.136

dt_b <- copy(dt)

system.time({
  dt_b[, ':='(d = a + b,
              e = b + c,
              f = a + c)]
})
#>    user  system elapsed 
#>   0.096   0.116   0.211

Обновить:

Одним интересным свойством этого является то, что разница во времени между := и `:=`() является относительной примерно в 1,5-2 раза. Если бы это было просто из-за служебной нагрузки, как некоторые предполагают, я бы заподозрил разницу во времени быть фиксированным значением?

library(data.table)


n <- 20000000
dt <- data.table(a = rnorm(n),
                 b = rnorm(n),
                 c = rnorm(n))

dt_a <- copy(dt)

system.time({
  dt_a[, d := a + b]
  dt_a[, e := b + c]
  dt_a[, f := a + c]
})
#>    user  system elapsed 
#>   0.163   0.208   0.371

dt_b <- copy(dt)

system.time({
  dt_b[, ':='(d = a + b,
              e = b + c,
              f = a + c)]
})
#>    user  system elapsed 
#>   0.284   0.404   0.688

Ответ 1

Некоторые сроки:

bench::mark(
    chaining = DT0[, d := a + b][, e := b + c][, f := a + c],
    assign = DT1[, c("d", "e", "f") := .(a+b, b+c, a+c)],
    assign2 = DT1.1[, ':=' (d, a + b)][, ':=' (e, b + c)][, ':=' (f, a + c)],
    use_set = {
        set(DT2, NULL, "d", DT2[["a"]]+DT2[["b"]])
        set(DT2, NULL, "e", DT2[["b"]]+DT2[["c"]])
        set(DT2, NULL, "f", DT2[["a"]]+DT2[["c"]])
    },
    functional = DT3[, ':='(d = a + b, e = b + c, f = a + c)]
)

тайминги и использование памяти:

  expression     min    mean  median     max 'itr/sec' mem_alloc  n_gc n_itr total_time result           memory      time   gc       
  <chr>      <bch:t> <bch:t> <bch:t> <bch:t>     <dbl> <bch:byt> <dbl> <int>   <bch:tm> <list>           <list>      <list> <list>   
1 chaining     180ms   180ms   180ms   180ms      5.54     458MB     1     1      180ms <data.table [20~ <Rprofmem ~ <bch:~ <tibble ~
2 assign       320ms   320ms   320ms   320ms      3.12     916MB     1     1      320ms <data.table [20~ <Rprofmem ~ <bch:~ <tibble ~
3 assign2      188ms   188ms   188ms   188ms      5.33     458MB     1     1      188ms <data.table [20~ <Rprofmem ~ <bch:~ <tibble ~
4 use_set      322ms   323ms   323ms   323ms      3.10     916MB     0     2      645ms <data.table [20~ <Rprofmem ~ <bch:~ <tibble ~
5 functional   331ms   331ms   331ms   331ms      3.02     916MB     1     1      331ms <data.table [20~ <Rprofmem ~ <bch:~ <tibble ~

данные:

library(data.table) #data.table_1.12.2  
set.seed(0L)
n <- 2e7
DT <- data.table(a=rnorm(n), b=rnorm(n), c=rnorm(n))
DT0 <- copy(DT)
DT1 <- copy(DT)
DT1.1 <- copy(DT)
DT2 <- copy(DT)
DT3 <- copy(DT)