Data.table: почему data.table быстрее с векторизованным подмножеством столбцов, чем с подмножеством столбцов списка

Созданный на 27 мар. 2019  ·  3Комментарии  ·  Источник: Rdatatable/data.table

Мне нравится этот материал data.table, в равной степени за его скорость выполнения и за экономичный способ написания сценариев.
Я использую его даже на маленьких столах.
Я регулярно подмножаю таблицы таким образом: DT[, .(id1, id5)]
а не так: DT[, c("id1", "id5")]

Сегодня я измерил скорость обоих и был поражен разницей в скорости на маленьких столах. Экономный метод намного медленнее.

Является ли эта разница чем-то преднамеренным?

Есть ли стремление сделать экономичный способ сходящимся по скорости исполнения к другому?
(Это считается, когда мне приходится повторяющимся образом подмножать несколько небольших таблиц.)

Убунту 18.04
R версия 3.5.3 (11 марта 2019 г.)
таблица данных 1.12.0
Оперативная память 32 ГБ
Процессор Intel® Core™ i7-8565U с тактовой частотой 1,80 ГГц × 8

library(data.table)
library(microbenchmark)
N  <- 2e8
K  <- 100
set.seed(1)
DT <- data.table(
  id1 = sample(sprintf("id%03d", 1:K), N, TRUE),               # large groups (char)
  id2 = sample(sprintf("id%03d", 1:K), N, TRUE),               # large groups (char)
  id3 = sample(sprintf("id%010d", 1:(N/K)), N, TRUE),       # small groups (char)
  id4 = sample(K,   N, TRUE),                                           # large groups (int)
  id5 = sample(K,   N, TRUE),                                           # large groups (int)
  id6 = sample(N/K, N, TRUE),                                          # small groups (int)
  v1 =  sample(5,   N, TRUE),                                           # int in range [1,5]
  v2 =  sample(5,   N, TRUE),                                           # int in range [1,5]
  v3 =  sample(round(runif(100, max = 100), 4), N, TRUE) # numeric e.g. 23.5749
)

microbenchmark(
  DT[, .(id1, id5)],
  DT[, c("id1", "id5")]
)
Unit: seconds
                  expr      min       lq     mean   median       uq      max neval
     DT[, .(id1, id5)] 1.588367 1.614645 1.929348 1.626847 1.659698 12.33872   100
 DT[, c("id1", "id5")] 1.592154 1.613800 1.937548 1.628082 2.184456 11.74581   100


N  <- 2e5
DT2 <- data.table(
  id1 = sample(sprintf("id%03d", 1:K), N, TRUE),                 # large groups (char)
  id2 = sample(sprintf("id%03d", 1:K), N, TRUE),                 # large groups (char)
  id3 = sample(sprintf("id%010d", 1:(N/K)), N, TRUE),         # small groups (char)
  id4 = sample(K,   N, TRUE),                                             # large groups (int)
  id5 = sample(K,   N, TRUE),                                             # large groups (int)
  id6 = sample(N/K, N, TRUE),                                            # small groups (int)
  v1 =  sample(5,   N, TRUE),                                             # int in range [1,5]
  v2 =  sample(5,   N, TRUE),                                             # int in range [1,5]
  v3 =  sample(round(runif(100, max = 100), 4), N, TRUE)   # numeric e.g. 23.5749
)

microbenchmark(
  DT2[, .(id1, id5)],
  DT2[, c("id1", "id5")]
)
Unit: microseconds
                   expr      min       lq      mean    median        uq      max neval
DT2[, .(id1, id5)] 1405.042 1461.561 1525.5314 1491.7885 1527.8955 2220.860   100
DT2[, c("id1", "id5")]  614.624  640.617  666.2426  659.0175  676.9355  906.966   100

Самый полезный комментарий

Вы можете исправить форматирование вашего сообщения, используя одну строку из трех обратных кавычек до и после фрагмента кода:

```
code
```

Это считается, когда мне приходится повторяющимся образом подмножать несколько небольших таблиц.

Я предполагаю, что повторный выбор столбцов из небольших таблиц - это то, чего следует и в большинстве случаев можно избежать...? Поскольку j в DT[i, j, by] поддерживает и оптимизирует такое большое разнообразие входных данных, я думаю, что некоторые накладные расходы на его синтаксический анализ являются естественными.


Что касается других способов решения вашей проблемы (и, возможно, это лучше подходит для переполнения стека, если вы хотите поговорить об этом подробнее)... В зависимости от того, что еще вы хотите сделать с таблицей, вы можете просто удалить другие столбцы , DT[, setdiff(names(DT), cols) := NULL] и продолжайте использовать DT напрямую.

Если вы все же предпочитаете использовать подмножество, захват указателей столбцов выполняется намного быстрее, чем любой вариант, который вы рассматривали здесь, хотя таким образом изменения результата повлияют на исходную таблицу:

library(data.table)
library(microbenchmark)
N <- 2e8
K <- 100
set.seed(1)
DT <- data.table(
id1 = sprintf("id%03d", 1:K), # large groups (char)
id2 = sprintf("id%03d", 1:K), # large groups (char)
id3 = sprintf("id%010d", 1:(N/K)), # small groups (char)
id4 = sample(K), # large groups (int)
id5 = sample(K), # large groups (int)
id6 = sample(N/K), # small groups (int)
v1 = sample(5), # int in range [1,5]
v2 = sample(5), # int in range [1,5]
v3 = round(runif(100, max = 100), 4), # numeric e.g. 23.5749
row = seq_len(N)
)

cols = c("id1", "id5")
microbenchmark(times = 3,
  expression = DT[, .(id1, id5)],
  index = DT[, c("id1", "id5")],
  dotdot = DT[, ..cols],
  oddball = setDT(lapply(setNames(cols, cols), function(x) DT[[x]]))[],
  oddball2 = setDT(unclass(DT)[cols])[]
)

Unit: microseconds
       expr         min           lq         mean      median           uq         max neval
 expression 1249753.580 1304355.3415 1417166.9297 1358957.103 1500873.6045 1642790.106     3
      index 1184056.302 1191334.4835 1396372.3483 1198612.665 1502530.3715 1806448.078     3
     dotdot 1084521.234 1240062.2370 1439680.6980 1395603.240 1617260.4300 1838917.620     3
    oddball      92.659     171.8635     568.5317     251.068     806.4680    1361.868     3
   oddball2      66.582     125.9505     150.7337     185.319     192.8095     200.300     3

(Я убрал рандомизацию из вашего примера и уменьшил тест в # раз, потому что был нетерпелив.)

Я так и не нашел способа напрямую вызвать подмножество списка R (которое используется после unclass выше).

Относительно «редактирования результата будет изменена исходная таблица», я имею в виду:

myDT = data.table(a = 1:2, b = 3:4)

# standard way
res <- myDT[, "a"]
res[, a := 0]
myDT
#    a b
# 1: 1 3
# 2: 2 4

# oddball, grabbing pointers
res2 <- setDT(unclass(myDT)["a"])
res2[, a := 0]
myDT
#    a b
# 1: 0 3
# 2: 0 4

Все 3 Комментарий

Вы можете исправить форматирование вашего сообщения, используя одну строку из трех обратных кавычек до и после фрагмента кода:

```
code
```

Это считается, когда мне приходится повторяющимся образом подмножать несколько небольших таблиц.

Я предполагаю, что повторный выбор столбцов из небольших таблиц - это то, чего следует и в большинстве случаев можно избежать...? Поскольку j в DT[i, j, by] поддерживает и оптимизирует такое большое разнообразие входных данных, я думаю, что некоторые накладные расходы на его синтаксический анализ являются естественными.


Что касается других способов решения вашей проблемы (и, возможно, это лучше подходит для переполнения стека, если вы хотите поговорить об этом подробнее)... В зависимости от того, что еще вы хотите сделать с таблицей, вы можете просто удалить другие столбцы , DT[, setdiff(names(DT), cols) := NULL] и продолжайте использовать DT напрямую.

Если вы все же предпочитаете использовать подмножество, захват указателей столбцов выполняется намного быстрее, чем любой вариант, который вы рассматривали здесь, хотя таким образом изменения результата повлияют на исходную таблицу:

library(data.table)
library(microbenchmark)
N <- 2e8
K <- 100
set.seed(1)
DT <- data.table(
id1 = sprintf("id%03d", 1:K), # large groups (char)
id2 = sprintf("id%03d", 1:K), # large groups (char)
id3 = sprintf("id%010d", 1:(N/K)), # small groups (char)
id4 = sample(K), # large groups (int)
id5 = sample(K), # large groups (int)
id6 = sample(N/K), # small groups (int)
v1 = sample(5), # int in range [1,5]
v2 = sample(5), # int in range [1,5]
v3 = round(runif(100, max = 100), 4), # numeric e.g. 23.5749
row = seq_len(N)
)

cols = c("id1", "id5")
microbenchmark(times = 3,
  expression = DT[, .(id1, id5)],
  index = DT[, c("id1", "id5")],
  dotdot = DT[, ..cols],
  oddball = setDT(lapply(setNames(cols, cols), function(x) DT[[x]]))[],
  oddball2 = setDT(unclass(DT)[cols])[]
)

Unit: microseconds
       expr         min           lq         mean      median           uq         max neval
 expression 1249753.580 1304355.3415 1417166.9297 1358957.103 1500873.6045 1642790.106     3
      index 1184056.302 1191334.4835 1396372.3483 1198612.665 1502530.3715 1806448.078     3
     dotdot 1084521.234 1240062.2370 1439680.6980 1395603.240 1617260.4300 1838917.620     3
    oddball      92.659     171.8635     568.5317     251.068     806.4680    1361.868     3
   oddball2      66.582     125.9505     150.7337     185.319     192.8095     200.300     3

(Я убрал рандомизацию из вашего примера и уменьшил тест в # раз, потому что был нетерпелив.)

Я так и не нашел способа напрямую вызвать подмножество списка R (которое используется после unclass выше).

Относительно «редактирования результата будет изменена исходная таблица», я имею в виду:

myDT = data.table(a = 1:2, b = 3:4)

# standard way
res <- myDT[, "a"]
res[, a := 0]
myDT
#    a b
# 1: 1 3
# 2: 2 4

# oddball, grabbing pointers
res2 <- setDT(unclass(myDT)["a"])
res2[, a := 0]
myDT
#    a b
# 1: 0 3
# 2: 0 4

Хорошо, сегодня я узнал что-то новое и быстрое (чудаки), и я обратил внимание на то, что существует компромисс между скоростью и экономным кодированием. Итак, стакан наполовину полон! Спасибо!

Я думаю, что # 852 связано

Была ли эта страница полезной?
0 / 5 - 0 рейтинги