【问题标题】:Techniques to Speed up rollapply with Custom Function (r)使用自定义函数加速 rollapply 的技术 (r)
【发布时间】:2019-07-02 00:57:28
【问题描述】:

我有一个 rollapply 函数,它做一些非常简单的事情,但是超过百万个数据点,这个简单的函数非常慢。我想知道是否可以提供信息来 rollapply 以了解如何进行下一次转换,而不是定义函数本身。

具体来说,我正在为基本的统计异常检测执行滚动窗口。

滚动应用功能:

minmax <- function(x) { max(x) - min(x) }

调用者:

mclapply(data[,eval(vars),with=F], 
         function(x) rollapply(x,width=winSize,FUN=minmax,fill=NA),
         mc.cores=8)

data 是 8 列 data.table,winsize 是 300

此调用在 8 个内核上大约需要 2 分钟。它是整体计算的主要瓶颈之一。但是我可以想象我们可以保持它们排序(按值和索引),然后在每次滑动时进行 Olog(n) 比较。

但是,我经常看到建议不要使用 for 循环并使用 lapply 的帖子。进一步优化的下一个合乎逻辑的步骤是什么?

【问题讨论】:

  • 如果您所指的帖子(建议lapply 循环使用)已有几年历史,那么他们可能会说因为for 循环使用速度较慢。这已被修复(R-3.0?),因此for 循环总是较慢的前提可能是不明智的。有时 使用for 循环更有意义(当您不想要副作用并且想要捕获循环中每次迭代的结果时),在这种情况下*apply 函数之一可能更有意义。
  • 当使用像mclapply 这样的并行函数时,我最关心的是内核之间传递了多少数据。例如,如果你从mc.cores=8 转到(比如说)=4,你的执行时间会加倍吗? =12(如果你有更多)呢?如果您没有看到使用更多内核明显提高性能,您可能会因为复制数据而遇到延迟(取决于您的并行设置)。因此,也许加快速度不仅仅与您的 UDF 相关(它的效率与原始形式一样高)。
  • 由于我是 r 新手,所以我不知道 for 循环中的性能变化。我将看到一个 for 循环排序窗口如何执行 w.r.t。滚动应用。有意识地选择了 8 个核心,因为有 8 个 rollapplies(和 8 个核心)。我打算换一台 36 核的新机器,也许我可以研究阿姆达尔定律的影响。在我弄清楚远程 r in ess 之后。感谢您的建议
  • ESS 中的远程 R:取决于您的需要。对我来说,我在远程系统上安装了 emacs/ess,所以 sshtmux (screen) 就足够了;当我需要查看绘图时,我使用rmote。我读过ESS-Remote,但从未尝试过。

标签: r optimization rollapply


【解决方案1】:

不确定这是否/如何应用于mclapply 环境,但您可以通过使用zoo 的优化rollmax 函数来获得一点加速。由于他们没有补充 rollmin,因此您需要适应。

minmax <- function(x) max(x) - min(x)
aa <- runif(1e4)
identical(
  zoo::rollapply(aa, width=100, FUN=minmax, fill=NA),
  zoo::rollmax(aa, k=100, fill=NA) + zoo::rollmax(-aa, k=100, fill=NA)
)
# [1] TRUE

microbenchmark::microbenchmark(
  minmax = zoo::rollapply(aa, width=100, FUN=minmax, fill=NA),
  dblmax = zoo::rollmax(aa, k=100, fill=NA) + zoo::rollmax(-aa, k=100, fill=NA)
)
# Unit: milliseconds
#    expr     min      lq     mean   median      uq      max neval
#  minmax 70.7426 76.0469 84.81481 77.99565 81.8047 148.8431   100
#  dblmax 15.6755 17.4501 19.09820 17.93665 18.8650  52.4849   100

(改进将取决于窗口大小,因此您的结果可能会有所不同,但我认为使用优化的函数zoo::rollmax 几乎总是优于每次调用 UDF。)

【讨论】:

  • 我不知道rollmax,我无法想象实现rollmin优化的工作量太大。
  • 只是:rollmin &lt;- function(x, ...) -zoo::rollmax(-x, ...).
  • 运行时间从 124 秒缩短到 65 秒...太棒了!
【解决方案2】:

如果您真的想尽可能提高性能,请使用 Rcpp。自定义循环是 C++ 的一个很好的用例,尤其是当您的函数非常简单时。

先结果再编码:

microbenchmark::microbenchmark(
  minmax = zoo::rollapply(aa, width=100, FUN=minmax, fill=NA),
  dblmax = zoo::rollmax(aa, k=100, fill=NA) + zoo::rollmax(-aa, k=100, fill=NA),
  cminmax = crollapply(aa, width=width), times = 10
)
    Unit: milliseconds
    expr       min         lq       mean    median         uq        max neval cld
  minmax 154.04630 162.728871 188.198416 173.13427 200.928005 298.568673    10   c
  dblmax  37.38127  38.541603  44.818505  41.42796  50.001888  61.024250    10  b 
 cminmax   2.31766   2.363676   2.406835   2.39237   2.438109   2.512162    10 a  

C++/Rcpp 代码:

#include <Rcpp.h>
#include <algorithm>
using namespace Rcpp;

// [[Rcpp::export]]
std::vector<double> crollapply(std::vector<double> aa, int width) {
  if(width > aa.size()) throw exception("width too large :(");
  int start_offset = (width-1) / 2;
  int back_offset = width / 2;
  std::vector<double> results(aa.size());
  int i=0;
  for(; i < start_offset; i++) {
    results[i] = NA_REAL;
  }
  for(; i < results.size() - back_offset; i++) {
    double min = *std::min_element(&aa[i - start_offset], &aa[i + back_offset + 1]);
    double max = *std::max_element(&aa[i - start_offset], &aa[i + back_offset + 1]);
    results[i] = max - min;
  }
  for(; i < results.size(); i++) {
    results[i] = NA_REAL;
  }
  return results;
}

R 代码:

library(dplyr)
library(zoo)
library(microbenchmark)
library(Rcpp)

sourceCpp("~/Desktop/temp.cpp")

minmax <- function(x) max(x) - min(x)
aa <- runif(1e4)
width <- 100
x1 <- zoo::rollapply(aa, width=width, FUN=minmax, fill=NA)
x3 <- crollapply(aa, width=width)
identical(x1,x3)

width <- 101
x1 <- zoo::rollapply(aa, width=width, FUN=minmax, fill=NA)
x3 <- crollapply(aa, width=width)
identical(x1,x3)

microbenchmark::microbenchmark(
  minmax = zoo::rollapply(aa, width=100, FUN=minmax, fill=NA),
  dblmax = zoo::rollmax(aa, k=100, fill=NA) + zoo::rollmax(-aa, k=100, fill=NA),
  cminmax = crollapply(aa, width=width), times = 10
)

【讨论】:

  • 要真正尽可能地提高性能,是否有助于删除 std:: 绑定并进行低级暴力破解? (这可能会变得荒谬......你在这里的加速令人印象深刻。)
  • 我不是 c++ 大师,所以这并不是一个真正的建议,而是另一个问题,@thc
  • 不,std 表示标准 c++ 库。它应该被优化得非常好,所以尽可能使用它;)
  • 我对它是什么有充分的了解,但没有体验过它在整体意义上有多好,所以感谢您的保证。我对RcppSugar 的体验是它很好,并且使代码更易于维护和可读,但它确实有轻微 性能损失,所以我预计std:: 的性能也会下降。谢谢!
猜你喜欢
  • 1970-01-01
  • 2012-08-22
  • 2021-10-22
  • 1970-01-01
  • 1970-01-01
  • 2013-04-09
  • 2016-01-30
  • 2014-09-22
  • 1970-01-01
相关资源
最近更新 更多