如何在不复制的情况下计算时间差

How to calculate time differences without copying

我想计算两个 POSIXct 对象之间的秒数差异而不复制它们。这应该不是问题,因为它们存储为 numeric UNIX 时间,因此只需要简单的减法即可。问题是 - 运算符是根据对象的 classes 调度的,并调用了 difftimedifftime 函数将每个输入向量复制两次:

> 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