Data.table: mengapa data.table lebih cepat dengan subset kolom vektor daripada subset kolom daftar

Dibuat pada 27 Mar 2019  ·  3Komentar  ·  Sumber: Rdatatable/data.table

Saya suka hal-hal data.table ini, merata untuk kecepatan eksekusi dan untuk cara scripting yang hemat.
Saya menggunakannya bahkan di meja kecil juga.
Saya secara teratur membuat subset tabel dengan cara ini: DT[, .(id1, id5)]
dan tidak seperti ini: DT[, c("id1", "id5")]

Hari ini saya mengukur kecepatan keduanya dan saya heran dengan perbedaan kecepatan di meja kecil. Metode pelit jauh lebih lambat.

Apakah perbedaan ini sesuatu yang disengaja?

Apakah ada aspirasi untuk membuat cara pelit untuk konvergen dalam hal kecepatan eksekusi yang lain?
(Ini penting ketika saya harus membuat subset beberapa tabel kecil dengan cara yang berulang.)

Ubuntu 18.04
R versi 3.5.3 (2019-03-11)
data.tabel 1.12.0
RAM 32GB
Intel® Core™ i7-8565U CPU @ 1.80GHz × 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

Komentar yang paling membantu

Anda dapat memperbaiki format posting Anda dengan menggunakan satu baris dari tiga backticks sebelum dan sesudah potongan kode:

```
code
```

Itu penting ketika saya harus membuat subset beberapa tabel kecil dengan cara yang berulang.

Saya kira berulang kali memilih kolom dari tabel kecil adalah sesuatu yang harus, dan dalam banyak kasus, dapat dihindari ...? Karena j di DT[i, j, by] mendukung dan mengoptimalkan berbagai macam input, saya pikir wajar jika ada beberapa overhead dalam menguraikannya.


Mengenai cara lain untuk mendekati masalah Anda (dan mungkin ini akan lebih cocok untuk Stack Overflow jika Anda ingin membicarakannya lebih lanjut) ... Tergantung pada apa lagi yang ingin Anda lakukan dengan tabel, Anda bisa menghapus col lainnya , DT[, setdiff(names(DT), cols) := NULL] dan terus menggunakan DT secara langsung.

Jika Anda masih lebih suka mengambil subset, mengambil pointer kolom jauh lebih cepat daripada salah satu opsi yang Anda pertimbangkan di sini, meskipun cara mengedit hasil ini akan memengaruhi tabel asli:

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

(Saya mengambil pengacakan dari contoh Anda dan mengurangi # kali dalam benchmark karena saya tidak sabar.)

Saya tidak pernah menemukan cara untuk langsung memanggil subset daftar R (yang digunakan setelah unclass di atas).

Mengenai "pengeditan hasil akan mengubah tabel asli", maksud saya:

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

Semua 3 komentar

Anda dapat memperbaiki format posting Anda dengan menggunakan satu baris dari tiga backticks sebelum dan sesudah potongan kode:

```
code
```

Itu penting ketika saya harus membuat subset beberapa tabel kecil dengan cara yang berulang.

Saya kira berulang kali memilih kolom dari tabel kecil adalah sesuatu yang harus, dan dalam banyak kasus, dapat dihindari ...? Karena j di DT[i, j, by] mendukung dan mengoptimalkan berbagai macam input, saya pikir wajar jika ada beberapa overhead dalam menguraikannya.


Mengenai cara lain untuk mendekati masalah Anda (dan mungkin ini akan lebih cocok untuk Stack Overflow jika Anda ingin membicarakannya lebih lanjut) ... Tergantung pada apa lagi yang ingin Anda lakukan dengan tabel, Anda bisa menghapus col lainnya , DT[, setdiff(names(DT), cols) := NULL] dan terus menggunakan DT secara langsung.

Jika Anda masih lebih suka mengambil subset, mengambil pointer kolom jauh lebih cepat daripada salah satu opsi yang Anda pertimbangkan di sini, meskipun cara mengedit hasil ini akan memengaruhi tabel asli:

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

(Saya mengambil pengacakan dari contoh Anda dan mengurangi # kali dalam benchmark karena saya tidak sabar.)

Saya tidak pernah menemukan cara untuk langsung memanggil subset daftar R (yang digunakan setelah unclass di atas).

Mengenai "pengeditan hasil akan mengubah tabel asli", maksud saya:

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

Oke, saya telah mempelajari sesuatu yang baru dan cepat (yang eksentrik) hari ini dan saya telah mencatat bahwa ada trade-off antara kecepatan dan pengkodean yang hemat. Jadi gelasnya setengah penuh! Terima kasih!

Saya kira #852 terkait

Apakah halaman ini membantu?
0 / 5 - 0 peringkat