Data.table: pourquoi data.table est plus rapide avec un sous-ensemble de colonnes vectorisées qu'avec un sous-ensemble de colonnes de liste

Créé le 27 mars 2019  ·  3Commentaires  ·  Source: Rdatatable/data.table

J'aime ce truc data.table, à la fois pour sa vitesse d'exécution et pour sa manière parcimonieuse de scripter.
Je l'utilise même sur de petites tables également.
Je sous-ensemble régulièrement des tables de cette façon : DT[, .(id1, id5)]
et pas de cette façon : DT[, c("id1", "id5")]

Aujourd'hui j'ai mesuré la vitesse des deux et j'ai été étonné de la différence de vitesse sur les petites tables. La méthode parcimonieuse est beaucoup plus lente.

Cette différence est-elle intentionnelle ?

Aspire-t-on à faire converger la voie parcimonieuse en termes de rapidité d'exécution vers l'autre ?
(Cela compte quand je dois sous-ensemble plusieurs petites tables de manière répétitive.)

Ubuntu 18.04
R version 3.5.3 (2019-03-11)
data.table 1.12.0
RAM 32 Go
Processeur Intel® Core™ i7-8565U à 1,80 GHz × 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

Commentaire le plus utile

Vous pouvez corriger la mise en forme de votre message en utilisant une seule ligne de trois backticks avant et après le morceau de code :

```
code
```

Ça compte quand je dois subdiviser plusieurs petites tables de manière répétitive.

Je suppose que la sélection répétée de colonnes à partir de petites tables est quelque chose qui devrait, et dans la plupart des cas, peut être évité... ? Étant donné que j dans DT[i, j, by] prend en charge et optimise une telle variété d'entrées, je pense qu'il est naturel qu'il y ait une surcharge lors de son analyse.


En ce qui concerne les autres façons d'aborder votre problème (et peut-être que cela conviendrait mieux à Stack Overflow si vous voulez en parler davantage) ... En fonction de ce que vous voulez faire d'autre avec la table, vous pouvez simplement supprimer les autres cols , DT[, setdiff(names(DT), cols) := NULL] et continuez à utiliser DT directement.

Si vous préférez toujours prendre le sous-ensemble, la saisie des pointeurs de colonne est beaucoup plus rapide que l'une ou l'autre des options que vous avez envisagées ici, bien que de cette façon, les modifications apportées au résultat affectent la table d'origine :

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

(J'ai retiré la randomisation de votre exemple et réduit # fois dans le benchmark parce que j'étais impatient.)

Je n'ai jamais trouvé de moyen d'appeler directement le sous-ensemble de liste de R (qui est utilisé après le unclass ci-dessus).

En ce qui concerne "les modifications apportées au résultat modifieront la table d'origine", je veux dire :

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

Tous les 3 commentaires

Vous pouvez corriger la mise en forme de votre message en utilisant une seule ligne de trois backticks avant et après le morceau de code :

```
code
```

Ça compte quand je dois subdiviser plusieurs petites tables de manière répétitive.

Je suppose que la sélection répétée de colonnes à partir de petites tables est quelque chose qui devrait, et dans la plupart des cas, peut être évité... ? Étant donné que j dans DT[i, j, by] prend en charge et optimise une telle variété d'entrées, je pense qu'il est naturel qu'il y ait une surcharge lors de son analyse.


En ce qui concerne les autres façons d'aborder votre problème (et peut-être que cela conviendrait mieux à Stack Overflow si vous voulez en parler davantage) ... En fonction de ce que vous voulez faire d'autre avec la table, vous pouvez simplement supprimer les autres cols , DT[, setdiff(names(DT), cols) := NULL] et continuez à utiliser DT directement.

Si vous préférez toujours prendre le sous-ensemble, la saisie des pointeurs de colonne est beaucoup plus rapide que l'une ou l'autre des options que vous avez envisagées ici, bien que de cette façon, les modifications apportées au résultat affectent la table d'origine :

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

(J'ai retiré la randomisation de votre exemple et réduit # fois dans le benchmark parce que j'étais impatient.)

Je n'ai jamais trouvé de moyen d'appeler directement le sous-ensemble de liste de R (qui est utilisé après le unclass ci-dessus).

En ce qui concerne "les modifications apportées au résultat modifieront la table d'origine", je veux dire :

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

Ok, j'ai appris quelque chose de nouveau et de rapide (les excentriques) aujourd'hui et j'ai pris note qu'il y a un compromis entre la vitesse et le codage parcimonieux. Le verre est donc à moitié plein ! Merci!

Je suppose que # 852 est lié

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

tcederquist picture tcederquist  ·  3Commentaires

mattdowle picture mattdowle  ·  3Commentaires

rafapereirabr picture rafapereirabr  ·  3Commentaires

arunsrinivasan picture arunsrinivasan  ·  3Commentaires

MichaelChirico picture MichaelChirico  ·  3Commentaires