'data.table' 中的 'j' 表达式是否自动并行化?

Are 'j'-expressions in 'data.table' automatically parallelised?

我应该如何理解 data.table 对象中内置的并行性?从 getDTthreads 函数文档来看,似乎使用 OpenMP 采用了共享内存并行性。这似乎相当低级,我想它只适用于某个子集 重载函数和运算符。

或者,data.table 是否足够聪明,可以为更复杂的表达式拆分工作?更具体地说,要并行化 j-表达式,我需要考虑哪些限制?

不要 运行 与 Stack Overflow 的问题政策有太多冲突,这里有一个例子。我经常想对一个巨大的 data.table 中的每个对象应用一个函数。例如,

library(data.table)
n <- 100000L
dt <- data.table(a = rnorm(n), b = rnorm(n))
dt[, c := sapply(a, function(x) paste(x, 'silly example')]

j 表达式中的 sapply 调用是否可以并行处理列 a 的块?还是按顺序工作的普通旧基 R sapply

如果是后者,那么在 j-表达式中嵌入 R 的众多并行计算框架之一是不是一种好方法?例如,我可以安全有效地调用 foreachfuture 等吗?在 j-expression?

来自?setDTthreads

Internally parallelized code is used in the following places:

  • between.c - between()
  • cj.c - CJ()
  • coalesce.c - fcoalesce()
  • fifelse.c - fifelse()
  • fread.c - fread()
  • forder.c, fsort.c, and reorder.c - forder() and related
  • froll.c, frolladaptive.c, and frollR.c - froll() and family
  • fwrite.c - fwrite()
  • gsumm.c - GForce in various places, see GForce
  • nafill.c - nafill()
  • subset.c - Used in [.data.table subsetting
  • types.c - Internal testing usage

我的理解是,您不应期望 data.table 在上述用例之外使用多线程。请注意,[.data.table 仅将多线程用于子集化,即在 i 表达式中而不是 j 表达式中。这大概只是为了加速关系和逻辑操作,如 x[!is.na(a) & a > 0].

j 表达式中,sumsapply 仍然只是 base::sumbase::sapply。您可以使用基准进行测试:

library("data.table")
setDTthreads(4L)

x <- data.table(a = rnorm(2^25))
microbenchmark::microbenchmark(sum(x$a), x[, sum(a)], times = 1000L)
Unit: milliseconds
        expr      min       lq     mean   median       uq      max neval
    sum(x$a) 51.61281 51.68317 51.95975 51.84204 52.09202 56.67213  1000
 x[, sum(a)] 51.78759 51.89054 52.18827 52.07291 52.33486 61.11378  1000
x <- data.table(a = seq_len(1e+04L))
microbenchmark::microbenchmark(sapply(x$a, paste, "is a good number"), x[, sapply(a, paste, "is a good number")], times = 1000L)
Unit: milliseconds
                                      expr      min      lq     mean   median       uq      max neval
    sapply(x$a, paste, "is a good number") 14.07403 15.7293 16.72879 16.31326 17.49072 45.62300  1000
 x[, sapply(a, paste, "is a good number")] 14.56324 15.9375 17.03164 16.48971 17.69045 45.99823  1000

很明显,简单地将代码放入 j 表达式中并不能提高性能。

data.table 确实识别并异常处理某些结构。例如,data.table 在看到 x[order(...)] 时使用它自己的基于基数的 forder 而不是 base::order。 (此功能现在有点多余,因为 base::order 的用户可以通过传递 method = "radix" 来请求 data.table 的基数排序。)我还没有看到此类异常的“主列表”。

至于在 j 表达式中使用例如 parallel::mclapply 是否可以提高性能,我认为答案(像往常一样)取决于您要做什么以及你的数据规模。最终,您必须进行自己的基准测试和分析才能找到答案。例如:

library("parallel")
cl <- makePSOCKcluster(4L)
microbenchmark::microbenchmark(x[, sapply(a, paste, "is a good number")], x[, parSapply(cl, a, paste, "is a good number")], times = 1000L)
stopCluster(cl)
Unit: milliseconds
                                             expr       min        lq      mean    median        uq      max neval
        x[, sapply(a, paste, "is a good number")] 14.553934 15.982681 17.105667 16.585525 17.864623 48.81276  1000
 x[, parSapply(cl, a, paste, "is a good number")]  7.675487  8.426607  9.022947  8.802454  9.334532 25.67957  1000

因此可以看到加速,但有时您会付出内存使用的代价。对于足够小的问题,与 R 级并行相关的开销肯定会超过性能优势。

您会找到关于集成 paralleldata.table 的好帖子(包括 的原因)here.