当我使用 `dplyr::mutate()` 时,为什么 `furrr::future_map_int()` 比 `purrr::map_int()` 慢?
Why is `furrr::future_map_int()` slower than `purrr::map_int()` when I use `dplyr::mutate()`?
我有一个 tibble,其中包含一个列表列,其中包含向量。我想创建一个新列来说明每个向量的 length。由于这个数据集很大(300 万行),我想使用 furrr
包来缩短一些处理时间。不过,好像purrr
比furrr
快。怎么会?
为了演示问题,我先模拟一些数据。模拟部分的代码不用看懂,与题目无关
数据模拟功能
library(stringi)
library(rrapply)
library(tibble)
simulate_data <- function(nrows) {
split_func <- function(x, n) {
unname(split(x, rep_len(1:n, length(x))))
}
randomly_subset_vec <- function(x) {
sample(x, sample(length(x), 1))
}
tibble::tibble(
col_a = rrapply(object = split_func(
x = setNames(1:(nrows * 5),
stringi::stri_rand_strings(nrows * 5,
2)),
n = nrows
),
f = randomly_subset_vec),
col_b = runif(nrows)
)
}
模拟数据
set.seed(2021)
my_data <- simulate_data(3e6) # takes about 1 minute to run on my machine
my_data
## # A tibble: 3,000,000 x 2
## col_a col_b
## <list> <dbl>
## 1 <int [3]> 0.786
## 2 <int [5]> 0.0199
## 3 <int [2]> 0.468
## 4 <int [2]> 0.270
## 5 <int [3]> 0.709
## 6 <int [2]> 0.643
## 7 <int [2]> 0.0837
## 8 <int [4]> 0.159
## 9 <int [2]> 0.429
## 10 <int [2]> 0.919
## # ... with 2,999,990 more rows
实际问题
我想改变一个新列 (length_col_a
),它将占 col_a
的长度。我要这样做两次。首先使用 purrr::map_int()
然后使用 furrr::future_map_int()
.
library(dplyr, warn.conflicts = T)
library(purrr)
library(furrr)
library(tictoc)
# first with purrr:
##################
tic()
my_data %>%
mutate(length_col_a = map_int(.x = col_a, .f = ~length(.x)))
## # A tibble: 3,000,000 x 3
## col_a col_b length_col_a
## <list> <dbl> <int>
## 1 <int [3]> 0.786 3
## 2 <int [5]> 0.0199 5
## 3 <int [2]> 0.468 2
## 4 <int [2]> 0.270 2
## 5 <int [3]> 0.709 3
## 6 <int [2]> 0.643 2
## 7 <int [2]> 0.0837 2
## 8 <int [4]> 0.159 4
## 9 <int [2]> 0.429 2
## 10 <int [2]> 0.919 2
## # ... with 2,999,990 more rows
toc()
## 6.16 sec elapsed
# and now with furrr:
####################
future::plan(future::multisession, workers = 2)
tic()
my_data %>%
mutate(length_col_a = future_map_int(col_a, length))
## # A tibble: 3,000,000 x 3
## col_a col_b length_col_a
## <list> <dbl> <int>
## 1 <int [3]> 0.786 3
## 2 <int [5]> 0.0199 5
## 3 <int [2]> 0.468 2
## 4 <int [2]> 0.270 2
## 5 <int [3]> 0.709 3
## 6 <int [2]> 0.643 2
## 7 <int [2]> 0.0837 2
## 8 <int [4]> 0.159 4
## 9 <int [2]> 0.429 2
## 10 <int [2]> 0.919 2
## # ... with 2,999,990 more rows
toc()
## 10.95 sec elapsed
我知道 tictoc
不是最准确的基准测试方法,但仍然 -- furrr
应该更快(as the vignette suggests), but it isn't. I've made sure that the data isn't grouped, since the author explained 而 furrr
不'适用于分组数据。那么对于 furrr
比 purrr
慢(或不是很快)的其他解释是什么?
编辑
我在 furrr
的 github 回购上发现 this issue 讨论了几乎相同的问题。然而,情况不同。在 github 问题中,被映射的函数是一个用户定义的函数,需要附加额外的包。所以作者解释说,每个furrr
worker在做计算之前都要附上需要的包。相比之下,我从 base R
映射 length()
函数,因此实际上应该没有附加任何包的开销。
此外,作者建议可能会出现问题,因为 plan(multisession)
无法在 RStudio 中工作。但是将 parallelly
包更新到开发版本可以解决这个问题。
remotes::install_github("HenrikBengtsson/parallelly", ref="develop")
不幸的是,这次更新对我的情况没有任何影响。
正如我在对原始 post 的评论中所论证的那样,我怀疑工作人员分发非常大的数据集会造成开销。
为了证实我的怀疑,我使用了 OP 使用的相同代码并进行了一次修改:我添加了 0.000001
的延迟,结果是:purrr --> 192.45 sec
和 furrr: 44.707 sec
(8 workers
)。 furrr
所用时间仅为 purrr
所用时间的 1/4 -- 与 1/8 相去甚远!
根据 OP 的要求,我的代码如下:
library(stringi)
library(rrapply)
library(tibble)
simulate_data <- function(nrows) {
split_func <- function(x, n) {
unname(split(x, rep_len(1:n, length(x))))
}
randomly_subset_vec <- function(x) {
sample(x, sample(length(x), 1))
}
tibble::tibble(
col_a = rrapply(object = split_func(
x = setNames(1:(nrows * 5),
stringi::stri_rand_strings(nrows * 5,
2)),
n = nrows
),
f = randomly_subset_vec),
col_b = runif(nrows)
)
}
set.seed(2021)
my_data <- simulate_data(3e6) # takes about 1 minute to run on my machine
my_data
library(dplyr, warn.conflicts = T)
library(purrr)
library(furrr)
library(tictoc)
# first with purrr:
##################
######## ----> DELAY <---- ########
f <- function(x) {Sys.sleep(0.000001); length(x)}
tic()
my_data %>%
mutate(length_col_a = map_int(.x = col_a, .f = ~ f(.x)))
toc()
plan(multisession, workers = 8)
tic()
my_data %>%
mutate(length_col_a = future_map_int(col_a, f))
toc()
我有一个 tibble,其中包含一个列表列,其中包含向量。我想创建一个新列来说明每个向量的 length。由于这个数据集很大(300 万行),我想使用 furrr
包来缩短一些处理时间。不过,好像purrr
比furrr
快。怎么会?
为了演示问题,我先模拟一些数据。模拟部分的代码不用看懂,与题目无关
数据模拟功能
library(stringi)
library(rrapply)
library(tibble)
simulate_data <- function(nrows) {
split_func <- function(x, n) {
unname(split(x, rep_len(1:n, length(x))))
}
randomly_subset_vec <- function(x) {
sample(x, sample(length(x), 1))
}
tibble::tibble(
col_a = rrapply(object = split_func(
x = setNames(1:(nrows * 5),
stringi::stri_rand_strings(nrows * 5,
2)),
n = nrows
),
f = randomly_subset_vec),
col_b = runif(nrows)
)
}
模拟数据
set.seed(2021)
my_data <- simulate_data(3e6) # takes about 1 minute to run on my machine
my_data
## # A tibble: 3,000,000 x 2
## col_a col_b
## <list> <dbl>
## 1 <int [3]> 0.786
## 2 <int [5]> 0.0199
## 3 <int [2]> 0.468
## 4 <int [2]> 0.270
## 5 <int [3]> 0.709
## 6 <int [2]> 0.643
## 7 <int [2]> 0.0837
## 8 <int [4]> 0.159
## 9 <int [2]> 0.429
## 10 <int [2]> 0.919
## # ... with 2,999,990 more rows
实际问题
我想改变一个新列 (length_col_a
),它将占 col_a
的长度。我要这样做两次。首先使用 purrr::map_int()
然后使用 furrr::future_map_int()
.
library(dplyr, warn.conflicts = T)
library(purrr)
library(furrr)
library(tictoc)
# first with purrr:
##################
tic()
my_data %>%
mutate(length_col_a = map_int(.x = col_a, .f = ~length(.x)))
## # A tibble: 3,000,000 x 3
## col_a col_b length_col_a
## <list> <dbl> <int>
## 1 <int [3]> 0.786 3
## 2 <int [5]> 0.0199 5
## 3 <int [2]> 0.468 2
## 4 <int [2]> 0.270 2
## 5 <int [3]> 0.709 3
## 6 <int [2]> 0.643 2
## 7 <int [2]> 0.0837 2
## 8 <int [4]> 0.159 4
## 9 <int [2]> 0.429 2
## 10 <int [2]> 0.919 2
## # ... with 2,999,990 more rows
toc()
## 6.16 sec elapsed
# and now with furrr:
####################
future::plan(future::multisession, workers = 2)
tic()
my_data %>%
mutate(length_col_a = future_map_int(col_a, length))
## # A tibble: 3,000,000 x 3
## col_a col_b length_col_a
## <list> <dbl> <int>
## 1 <int [3]> 0.786 3
## 2 <int [5]> 0.0199 5
## 3 <int [2]> 0.468 2
## 4 <int [2]> 0.270 2
## 5 <int [3]> 0.709 3
## 6 <int [2]> 0.643 2
## 7 <int [2]> 0.0837 2
## 8 <int [4]> 0.159 4
## 9 <int [2]> 0.429 2
## 10 <int [2]> 0.919 2
## # ... with 2,999,990 more rows
toc()
## 10.95 sec elapsed
我知道 tictoc
不是最准确的基准测试方法,但仍然 -- furrr
应该更快(as the vignette suggests), but it isn't. I've made sure that the data isn't grouped, since the author explained 而 furrr
不'适用于分组数据。那么对于 furrr
比 purrr
慢(或不是很快)的其他解释是什么?
编辑
我在 furrr
的 github 回购上发现 this issue 讨论了几乎相同的问题。然而,情况不同。在 github 问题中,被映射的函数是一个用户定义的函数,需要附加额外的包。所以作者解释说,每个furrr
worker在做计算之前都要附上需要的包。相比之下,我从 base R
映射 length()
函数,因此实际上应该没有附加任何包的开销。
此外,作者建议可能会出现问题,因为 plan(multisession)
无法在 RStudio 中工作。但是将 parallelly
包更新到开发版本可以解决这个问题。
remotes::install_github("HenrikBengtsson/parallelly", ref="develop")
不幸的是,这次更新对我的情况没有任何影响。
正如我在对原始 post 的评论中所论证的那样,我怀疑工作人员分发非常大的数据集会造成开销。
为了证实我的怀疑,我使用了 OP 使用的相同代码并进行了一次修改:我添加了 0.000001
的延迟,结果是:purrr --> 192.45 sec
和 furrr: 44.707 sec
(8 workers
)。 furrr
所用时间仅为 purrr
所用时间的 1/4 -- 与 1/8 相去甚远!
根据 OP 的要求,我的代码如下:
library(stringi)
library(rrapply)
library(tibble)
simulate_data <- function(nrows) {
split_func <- function(x, n) {
unname(split(x, rep_len(1:n, length(x))))
}
randomly_subset_vec <- function(x) {
sample(x, sample(length(x), 1))
}
tibble::tibble(
col_a = rrapply(object = split_func(
x = setNames(1:(nrows * 5),
stringi::stri_rand_strings(nrows * 5,
2)),
n = nrows
),
f = randomly_subset_vec),
col_b = runif(nrows)
)
}
set.seed(2021)
my_data <- simulate_data(3e6) # takes about 1 minute to run on my machine
my_data
library(dplyr, warn.conflicts = T)
library(purrr)
library(furrr)
library(tictoc)
# first with purrr:
##################
######## ----> DELAY <---- ########
f <- function(x) {Sys.sleep(0.000001); length(x)}
tic()
my_data %>%
mutate(length_col_a = map_int(.x = col_a, .f = ~ f(.x)))
toc()
plan(multisession, workers = 8)
tic()
my_data %>%
mutate(length_col_a = future_map_int(col_a, f))
toc()