如何在不复制的情况下计算时间差
How to calculate time differences without copying
我想计算两个 POSIXct 对象之间的秒数差异而不复制它们。这应该不是问题,因为它们存储为 numeric
UNIX 时间,因此只需要简单的减法即可。问题是 -
运算符是根据对象的 classes 调度的,并调用了 difftime
。 difftime
函数将每个输入向量复制两次:
> a <- as.POSIXct(runif(1e6, 0, 1000), origin = '1970-01-01')
> b <- as.POSIXct(runif(1e6, 0, 1000), origin = '1970-01-01')
> a_trace <- tracemem(a)
> b_trace <- tracemem(b)
> z <- a - b
tracemem[0x000000004c082470 -> 0x000007fff54e0010]: difftime -.POSIXt
tracemem[0x000007fff8c80010 -> 0x000007ffe9490010]: difftime -.POSIXt
tracemem[0x000007ffe9490010 -> 0x000007ffe8530010]: structure .difftime difftime -.POSIXt
tracemem[0x000007ffe8530010 -> 0x000007ffe7d80010]: structure .difftime difftime -.POSIXt
另一个问题是默认情况下 difftime
可能会选择秒以外的输出单位。通过使用 units 参数显式调用它可以避免这种情况,但仍会生成四个副本:
> z <- difftime(a, b, units = 'secs')
tracemem[0x000000004c082470 -> 0x000007ffe70a0010]: difftime
tracemem[0x000007fff8c80010 -> 0x000007ffe68f0010]: difftime
tracemem[0x000007ffe68f0010 -> 0x000007ffde890010]: structure .difftime difftime
tracemem[0x000007ffde890010 -> 0x000007ffde0e0010]: structure .difftime difftime
此外,生成的对象是 class difftime
,而不是普通的 numeric
。使用 base R,需要额外的结果副本来消除 difftime
class:
> z_trace <- tracemem(z)
> class(z) <- NULL
tracemem[0x000007ffb28e0010 -> 0x000007ffb2130010]:
使用 data.table::setattr
我设计了以下功能:
fast_difftime <- function(a, b) {
classA <- attr(a, 'class')
classB <- attr(b, 'class')
on.exit({
data.table::setattr(a, 'class', classA)
data.table::setattr(b, 'class', classB)
})
data.table::setattr(a, 'class', NULL)
data.table::setattr(b, 'class', NULL)
a - b
}
这避免了复制,而且速度更快:
> microbenchmark::microbenchmark(fast_difftime(a, b), as.numeric(difftime(a, b, units = "secs")))
Unit: milliseconds
expr min lq mean median uq max neval cld
fast_difftime(a, b) 1.728555 4.213836 5.97520 4.392592 6.365763 127.1690 100 a
as.numeric(difftime(a, b, units = "secs")) 6.643092 19.352806 24.54938 19.861066 23.298505 137.0776 100 b
但是,我不喜欢我必须就地修改输入向量的属性,只是为了避免方法分派。有没有更好的方法?
Rcpp 是一个选项,因为您可以忽略 class 属性:
library(Rcpp)
cppFunction(
'NumericVector mydiff(const NumericVector x, const NumericVector y) {
return x - y;
}
')
microbenchmark::microbenchmark(fast_difftime(a, b), mydiff(a, b))
#Unit: milliseconds
# expr min lq mean median uq max neval cld
# fast_difftime(a, b) 2.248841 2.291861 3.489386 2.326559 2.379951 46.69430 100 a
# mydiff(a, b) 2.165105 2.209661 3.089114 2.229380 2.272144 10.96047 100 a
我想计算两个 POSIXct 对象之间的秒数差异而不复制它们。这应该不是问题,因为它们存储为 numeric
UNIX 时间,因此只需要简单的减法即可。问题是 -
运算符是根据对象的 classes 调度的,并调用了 difftime
。 difftime
函数将每个输入向量复制两次:
> a <- as.POSIXct(runif(1e6, 0, 1000), origin = '1970-01-01')
> b <- as.POSIXct(runif(1e6, 0, 1000), origin = '1970-01-01')
> a_trace <- tracemem(a)
> b_trace <- tracemem(b)
> z <- a - b
tracemem[0x000000004c082470 -> 0x000007fff54e0010]: difftime -.POSIXt
tracemem[0x000007fff8c80010 -> 0x000007ffe9490010]: difftime -.POSIXt
tracemem[0x000007ffe9490010 -> 0x000007ffe8530010]: structure .difftime difftime -.POSIXt
tracemem[0x000007ffe8530010 -> 0x000007ffe7d80010]: structure .difftime difftime -.POSIXt
另一个问题是默认情况下 difftime
可能会选择秒以外的输出单位。通过使用 units 参数显式调用它可以避免这种情况,但仍会生成四个副本:
> z <- difftime(a, b, units = 'secs')
tracemem[0x000000004c082470 -> 0x000007ffe70a0010]: difftime
tracemem[0x000007fff8c80010 -> 0x000007ffe68f0010]: difftime
tracemem[0x000007ffe68f0010 -> 0x000007ffde890010]: structure .difftime difftime
tracemem[0x000007ffde890010 -> 0x000007ffde0e0010]: structure .difftime difftime
此外,生成的对象是 class difftime
,而不是普通的 numeric
。使用 base R,需要额外的结果副本来消除 difftime
class:
> z_trace <- tracemem(z)
> class(z) <- NULL
tracemem[0x000007ffb28e0010 -> 0x000007ffb2130010]:
使用 data.table::setattr
我设计了以下功能:
fast_difftime <- function(a, b) {
classA <- attr(a, 'class')
classB <- attr(b, 'class')
on.exit({
data.table::setattr(a, 'class', classA)
data.table::setattr(b, 'class', classB)
})
data.table::setattr(a, 'class', NULL)
data.table::setattr(b, 'class', NULL)
a - b
}
这避免了复制,而且速度更快:
> microbenchmark::microbenchmark(fast_difftime(a, b), as.numeric(difftime(a, b, units = "secs")))
Unit: milliseconds
expr min lq mean median uq max neval cld
fast_difftime(a, b) 1.728555 4.213836 5.97520 4.392592 6.365763 127.1690 100 a
as.numeric(difftime(a, b, units = "secs")) 6.643092 19.352806 24.54938 19.861066 23.298505 137.0776 100 b
但是,我不喜欢我必须就地修改输入向量的属性,只是为了避免方法分派。有没有更好的方法?
Rcpp 是一个选项,因为您可以忽略 class 属性:
library(Rcpp)
cppFunction(
'NumericVector mydiff(const NumericVector x, const NumericVector y) {
return x - y;
}
')
microbenchmark::microbenchmark(fast_difftime(a, b), mydiff(a, b))
#Unit: milliseconds
# expr min lq mean median uq max neval cld
# fast_difftime(a, b) 2.248841 2.291861 3.489386 2.326559 2.379951 46.69430 100 a
# mydiff(a, b) 2.165105 2.209661 3.089114 2.229380 2.272144 10.96047 100 a