'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 的众多并行计算框架之一是不是一种好方法?例如,我可以安全有效地调用 foreach
、future
等吗?在 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
表达式中,sum
和 sapply
仍然只是 base::sum
和 base::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 级并行相关的开销肯定会超过性能优势。
您会找到关于集成 parallel
和 data.table
的好帖子(包括 不 的原因)here.
我应该如何理解 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 的众多并行计算框架之一是不是一种好方法?例如,我可以安全有效地调用 foreach
、future
等吗?在 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
, andreorder.c
-forder()
and relatedfroll.c
,frolladaptive.c
, andfrollR.c
-froll()
and familyfwrite.c
-fwrite()
gsumm.c
- GForce in various places, see GForcenafill.c
-nafill()
subset.c
- Used in[.data.table
subsettingtypes.c
- Internal testing usage
我的理解是,您不应期望 data.table
在上述用例之外使用多线程。请注意,[.data.table
仅将多线程用于子集化,即在 i
表达式中而不是 j
表达式中。这大概只是为了加速关系和逻辑操作,如 x[!is.na(a) & a > 0]
.
在 j
表达式中,sum
和 sapply
仍然只是 base::sum
和 base::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 级并行相关的开销肯定会超过性能优势。
您会找到关于集成 parallel
和 data.table
的好帖子(包括 不 的原因)here.