我想计算两个POSIXct对象之间的秒差,而无需复制它们。这应该不成问题,因为它们以UNIX时间numeric
的形式存储,因此只需进行简单的减法即可。问题在于,-
操作符是根据对象的类调度的,并且会调用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
此外,生成的对象属于difftime
类,而不是普通的numeric
类。使用基数R,结果的附加副本对于消除difftime
类是必需的:
> 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
但是,我不喜欢必须就地修改输入向量的属性这一事实,只是为了避免方法分派。有更好的方法吗?
答案 0 :(得分:1)
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