出于某种原因,这个操作似乎显示data.table分配一个新列大约是基数R的一半。这是否有原因?
require(microbenchmark)
require(data.table)
DT = data.table(a = runif(1000000), b = rnorm(1000000))
DF = data.frame(a = runif(1000000), b = rnorm(1000000))
microbenchmark(
DT[,keycol := seq(1,nrow(DT))],
DF$keycol <- seq(1,nrow(DF)),
times = 2)
Unit: microseconds
expr min lq mean median uq max neval
DT[, `:=`(keycol, seq(1, nrow(DT)))] 901.109 901.109 921.1220 921.1220 941.135 941.135 2
DF$keycol <- seq(1, nrow(DF)) 487.844 487.844 527.1865 527.1865 566.529 566.529 2
这是我的R版本,使用data.table版本1.10.4:
> version
_
platform x86_64-w64-mingw32
arch x86_64
os mingw32
system x86_64, mingw32
status
major 3
minor 3.3
year 2017
month 03
day 06
svn rev 72310
language R
version.string R version 3.3.3 (2017-03-06)
nickname Another Canoe
答案 0 :(得分:4)
很难对以ms / us范围运行的任务进行基准测试,因为很难正确地测量实际时间,而微小的扰动会对结果产生很大的影响。
我通常不会使用任何基准测试包。我建议不要使用任何特别用于更新的引用类型操作。此外,在运行代码(使用times=100L
)时,虽然我在一起运行时获得了相同的时序差异,但当我分别对DT
和DF
代码进行基准测试时,时序或多或少相同。不知道为什么。
因此,我建议运行这样的事情:
require(data.table)
set.seed(1L)
N <- 1e6L
DT = data.table(a = runif(N), b = rnorm(N))
DF = data.frame(a = runif(N), b = rnorm(N))
runs <- 100:51
t_dt <- sapply(runs, function(k) {
# cat("k=",k,"\n",sep="")
DTlist <- lapply(1:k, function(x) copy(DT))
t0 = proc.time()
for (i in 1:k) DTlist[[i]][, keycol := seq(1, nrow(DT))]
(proc.time()-t0)[["elapsed"]]
})
t_df <- sapply(runs, function(k) {
# cat("k=",k,"\n",sep="")
DFlist <- lapply(1:k, function(x) copy(DF))
t0 = proc.time()
for (i in 1:k) DFlist[[i]]$keycol <- seq(1, nrow(DF))
(proc.time()-t0)[["elapsed"]]
})
我还保留了
for
循环大小变量,以便平均由于运行次数差异而可能出现的变化。我将下限保持在51,因为它似乎是一个相当大的数字,其中基准测试结果将是有意义的(更重要的是,我不会没有耐心: - ))。
我们可以直接调用C函数assign
,它是通过引用更新的实际函数,以避免导致运行时的任何其他影响,包括[.data.table
调用开销。这将作为基线。
t_dt_base <- sapply(runs, function(k) {
# cat("k=",k,"\n",sep="")
DTlist <- lapply(1:k, function(x) copy(DT))
t0 = proc.time()
for (i in 1:k) .Call("Cassign", DTlist[[i]], NULL, 3L, "keycol", list(seq(1, nrow(DT))), FALSE)
(proc.time()-t0)[["elapsed"]]
})
ans <- data.table(dt=t_dt/(runs), df=t_df/(runs), dt_base=t_dt_base/(runs)) # average within runs
# fwrite(ans, "timings.csv", sep=",")
(t_mean <- sapply(ans, mean)) # average across runs
# dt df dt_base
# 0.003250907 0.002789930 0.002735729
基线运行时(从直接调用到assign
)与df
大致相同。但是,0.000515178
与基线之间存在dt
秒的差异,我们可以将其标记为[.data.table
开销(可能是列表的[[
访问权限) 。
使用N <- 1e7L
和runs <- 10:5
运行返回:
dt df dt_base
0.01697659 0.01468419 0.01479067
导致0.00218592
(&gt;&gt; 0.0005)的差异。在我看来,还有其他因素取决于data.table(?)的大小,似乎有助于运行时...我没有时间调查该ATM。但是,希望这有点帮助。
PS:在处理这个Q时,我发现在这样的场景中可以避免(深度)副本:
x <- 1:5
.Internal(inspect(x))
# @7fdde2a4ed20 13 INTSXP g0c3 [NAM(1)] (len=5, tl=0) 1,2,3,4,5
tracemem(x)
dt <- data.table(a=1:5, b=6:10)
dt[, c := x] # 'x' is deep copied here but should be avoided
这是因为this line导致NAM(1)递增到NAM(2)(即,两个符号现在绑定到该值)。并且data.table在内部检查它并在NAM(2)时进行深度复制。这可能是可以避免的。我会尽快提出问题。
注意:这是在R-console(来自iTerm)中运行的。 RStudio似乎默认创建NAM(2),即使对于向量,这很奇怪,我不知道为什么。但这确实意味着即使我们修复了这种情况,RStudio仍然会深层复制。
答案 1 :(得分:2)
我也对这个差异有多大印象深刻...我猜它是[.data.table
顺便说一下,你没有正确地进行基准测试 - 更平坦的比较不会在某些时候覆盖列,而是每次都从头开始:
set.seed(102340)
times = matrix(nrow = 500, ncol = 2)
colnames(times) = c('DT', 'DF')
for (ii in seq_len(nrow(times))) {
DT = data.table(a = runif(1000000), b = rnorm(1000000))
DF = data.frame(a = runif(1000000), b = rnorm(1000000))
TT0 = get_nanotime()
DT[ , keycol := seq(1, nrow(DT))]
TT1 = get_nanotime()
delDT = TT1 - TT0
TT0 = get_nanotime()
DF$keycol <- seq(1,nrow(DF))
TT1 = get_nanotime()
delDF = TT1 - TT0
times[ii, ] = c(delDT, delDF)
}
summary(times)
# DT DF
# Min. : 1617687 Min. : 420502
# 1st Qu.: 2205314 1st Qu.: 447691
# Median : 3297872 Median : 464019
# Mean : 5277059 Mean : 594214
# 3rd Qu.: 4291291 3rd Qu.: 578034
# Max. :75731819 Max. :2224713
使用seq_len(nrow(DT))
代替seq(1, nrow(DT))
时,两种方法都更快。
差异的一个不错的部分似乎被归结为[.data.table
:
set.seed(102340)
ns = as.integer(10^(1:7))
ratios = numeric(length(ns))
for (nn in seq_along(ns)) {
times = matrix(nrow = 500L, ncol = 2L)
for (ii in seq_len(nrow(times))) {
DT = data.table(a = runif(ns[nn]),
b = rnorm(ns[nn]))
DF = data.frame(a = runif(ns[nn]),
b = rnorm(ns[nn]))
TT0.1 = get_nanotime()
DT[ , keycol := seq_len(nrow(DT))]
TT1.1 = get_nanotime()
delDT = TT1.1 - TT0.1
TT0.2 = get_nanotime()
DF$keycol <- seq(1,nrow(DF))
TT1.2 = get_nanotime()
delDF = TT1.2 - TT0.2
times[ii, ] = c(delDT, delDF)
}
ratios[nn] = median(times[ , 1L])/median(times[ , 2L])
print(ratios)
}
plot(log10(ns), ratios, type = 'b', lwd = 3L, xaxt = 'n',
xlab = '# Rows', ylab = 'DT time / DF time',
main = 'Ratio of DT assignment time\nvs. DF Assignment Time')
axis(side = 1L, at = 1:7, labels = ns)
abline(h = 1, lty = 2L, col = 'red')
答案 2 :(得分:1)
当N变大时,时间变得相当。
require(microbenchmark)
require(data.table)
N <- 1e7
DT = data.table(a = runif(N), b = rnorm(N))
DF = data.frame(a = runif(N), b = rnorm(N))
#force(DT)
ans <- capture.output(microbenchmark(
DT[,keycol := seq_len(.N)],
DT$keycol <- seq_len(nrow(DT)), #as mentioned in vignette, this is slow
DT[["keycol"]] <- seq_len(nrow(DT)),
DT[,"keycol"] <- seq_len(nrow(DT)),
DF$keycol <- seq_len(nrow(DF)),
DF[["keycol"]] <- seq_len(nrow(DF)),
DF[,"keycol"] <- seq_len(nrow(DF)),
times = 20L))
message(paste0("#",ans,"\n"))
#Unit: milliseconds
# expr min lq mean median uq max neval
# DT[, `:=`(keycol, seq_len(.N))] 16.1415 16.96355 29.26518 17.35340 21.91285 232.7037 20
# DT$keycol <- seq_len(nrow(DT)) 233.7527 291.84105 385.04133 419.14105 451.05655 469.3172 20
# DT[["keycol"]] <- seq_len(nrow(DT)) 15.5652 16.41960 18.81244 16.99350 20.12640 35.2602 20
# DT[, "keycol"] <- seq_len(nrow(DT)) 134.1463 136.92965 197.58160 166.53125 206.34465 394.7461 20
# DF$keycol <- seq_len(nrow(DF)) 14.5780 16.33775 19.65723 17.04340 22.78940 39.9137 20
# DF[["keycol"]] <- seq_len(nrow(DF)) 14.4700 16.11845 38.83084 16.49010 22.83845 220.2109 20
# DF[, "keycol"] <- seq_len(nrow(DF)) 15.1030 16.45990 26.03781 16.97035 21.90650 137.9879 20
R规格:
sessionInfo()
#R version 3.3.2 (2016-10-31)
#Platform: x86_64-w64-mingw32/x64 (64-bit)
#Running under: Windows 7 x64 (build 7601) Service Pack 1
#
#locale:
#[1] LC_COLLATE=English_Singapore.1252 LC_CTYPE=English_Singapore.1252 LC_MONETARY=English_Singapore.1252 LC_NUMERIC=C LC_TIME=English_Singapore.1252
#
#attached base packages:
#[1] stats graphics grDevices utils datasets methods base
#
#other attached packages:
#[1] data.table_1.10.0 microbenchmark_1.4-2.1
#
#loaded via a namespace (and not attached):
# [1] Rcpp_0.12.8 assertthat_0.1 grid_3.3.2 R6_2.2.0 plyr_1.8.4 gtable_0.2.0 magrittr_1.5 scales_0.4.1
# [9] ggplot2_2.2.1 httr_1.2.1 lazyeval_0.2.0 rstudioapi_0.6 tools_3.3.2 munsell_0.4.3 RStudioShortKeys_0.1.0 colorspace_1.3-2
#[17] tibble_1.2