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

时间:2018-07-19 13:08:54

标签: r datetime dispatch

我想计算两个POSIXct对象之间的秒差,而无需复制它们。这应该不成问题,因为它们以UNIX时间numeric的形式存储,因此只需进行简单的减法即可。问题在于,-操作符是根据对象的类调度的,并且会调用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 

此外,生成的对象属于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

但是,我不喜欢必须就地修改输入向量的属性这一事实,只是为了避免方法分派。有更好的方法吗?

1 个答案:

答案 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