0Pricing
R Academy · 课时

furrr:并行执行 purrr 操作

使用 future_map() 直接替换 map(),立即实现并行化

furrr:并行执行 purrr 操作 是 CoddyKit 上的免费 R Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 R Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 R Academy 课程共包含 4 节课。

furrr:并行 purrr

furrr(future + purrr)为所有 purrr::map_*() 函数提供了可直接替换的并行版本。设置好 plan() 后,只需将 map() 替换为 future_map(),处理流程无需任何结构性改动即可并行运行。

library(furrr)
library(future)

# Set up parallel workers
plan(multisession, workers = 4)

# Sequential (purrr)
# result <- purrr::map(1:8, ~.x^2)

# Parallel (furrr) — identical API
result <- future_map(1:8, ~.x^2)
cat(unlist(result), '\n')  # 1 4 9 16 25 36 49 64

plan(sequential)

plan(multisession, workers = 4)

在 plan() 中明确指定 workers,可以限制并行 R 会话的数量。对于受 CPU 限制的任务,通常使用 workers = parallel::detectCores() - 1,以便为操作系统留出一个核心。

library(furrr)
library(future)
library(parallel)

# Explicit worker count
n_workers <- max(1, detectCores() - 1)
plan(multisession, workers = n_workers)

cat('Active workers:', nbrOfWorkers(), '\n')
cat('Strategy:', class(plan())[1], '\n')

# Run a simple parallel task
results <- future_map_dbl(1:8, ~sqrt(.x))
cat(round(results, 3), '\n')

plan(sequential)

future_map_dbl 与类型变体

与 purrr 类似,furrr 提供了类型变体:future_map_dbl()、future_map_int()、future_map_chr() 和 future_map_lgl()。它们会强制统一返回类型,并返回原子向量而不是列表。

library(furrr)
plan(multisession, workers = 2)

# Returns a numeric vector
square_roots <- future_map_dbl(1:6, ~sqrt(.x))
cat('dbl:', round(square_roots, 3), '\n')

# Returns an integer vector
counts <- future_map_int(list('hello', 'world', 'R'), nchar)
cat('int:', counts, '\n')

# Returns a character vector
formatted <- future_map_chr(c(1.23, 4.56, 7.89), ~sprintf('%.1f', .x))
cat('chr:', formatted, '\n')

# Returns a logical vector
positive <- future_map_lgl(-3:3, ~.x > 0)
cat('lgl:', positive, '\n')

plan(sequential)

future_map2:双输入映射

future_map2(.x, .y, .f) 会并行遍历两个列表或向量,并将对应的元素对传递给函数。它是 purrr::map2() 的并行版本。

library(furrr)
plan(multisession, workers = 2)

# Simulate different sample sizes and means
sizes <- c(100, 200, 300, 400)
means <- c(0, 5, -3, 10)

# future_map2 passes each (n, mu) pair to rnorm
samples <- future_map2(sizes, means, ~rnorm(.x, mean = .y))

# Verify: each element has the expected length and approximate mean
for (i in seq_along(samples)) {
  cat('n=', sizes[i], 'target_mean=', means[i],
      'observed_mean=', round(mean(samples[[i]]), 2), '\n')
}

plan(sequential)

furrr_options:控制行为

furrr_options() 作为 .options 参数传递给任意 future_map_*() 调用。最重要的设置是 seed = TRUE,它会启用 L'Ecuyer-CMRG 并行随机数生成器,从而确保不同工作进程生成可复现的随机数。

library(furrr)
plan(multisession, workers = 2)

# Without seed: results differ each run
r1 <- future_map_dbl(1:4, ~rnorm(1))
r2 <- future_map_dbl(1:4, ~rnorm(1))
cat('Without seed - same?', identical(r1, r2), '\n')

# With seed: reproducible
opts <- furrr_options(seed = 42L)
r3 <- future_map_dbl(1:4, ~rnorm(1), .options = opts)
r4 <- future_map_dbl(1:4, ~rnorm(1), .options = opts)
cat('With seed - same?', identical(r3, r4), '\n')
cat('r3:', round(r3, 4), '\n')

plan(sequential)

使用 progressr 报告进度

progressr 包可以与 furrr 集成,在并行执行期间显示进度条。请将代码放在 with_progress() 中,并在映射函数内部创建一个 progressor()。

library(furrr)
library(progressr)
plan(multisession, workers = 2)

# Enable progress reporting
handlers(global = TRUE)  # show progress in console

with_progress({
  p <- progressor(steps = 8)

  results <- future_map(1:8, function(i) {
    p()  # increment the progress bar
    Sys.sleep(0.1)
    i^2
  })
})

cat('Results:', unlist(results), '\n')

plan(sequential)

furrr 中的全局变量

与 future 包类似,furrr 会自动检测 .f 内部引用的全局变量。您可以使用 furrr_options(globals = c('var1', 'var2')) 覆盖此行为,明确指定要发送的全局变量,从而减少大型环境带来的开销。

library(furrr)
plan(multisession, workers = 2)

# Global variables auto-detected
scale_factor <- 10
offset <- 5

result <- future_map_dbl(
  1:6,
  function(x) x * scale_factor + offset
)
cat(result, '\n')  # 15 25 35 45 55 65

# Explicit globals control
opts <- furrr_options(
  globals = c('scale_factor', 'offset'),
  seed = FALSE
)
result2 <- future_map_dbl(
  1:6,
  function(x) x * scale_factor + offset,
  .options = opts
)
cat('Manual globals:', result2, '\n')

plan(sequential)

future_pmap:多参数映射

future_pmap(.l, .f) 是 purrr::pmap() 的并行版本。它接收一个由向量或列表组成的列表,并将对应的行作为带名称的参数传递,从而可以针对多种参数组合进行并行计算。

library(furrr)
plan(multisession, workers = 2)

# Parameter grid
params <- list(
  n    = c(50, 100, 150, 200),
  mean = c(0, 1, 2, 3),
  sd   = c(1, 2, 1, 0.5)
)

# future_pmap passes each row as arguments to rnorm
samples <- future_pmap(params, function(n, mean, sd) {
  x <- rnorm(n, mean = mean, sd = sd)
  c(obs_mean = round(mean(x), 3), obs_sd = round(sd(x), 3))
})

for (i in seq_along(samples)) {
  cat('n=', params$n[i], ':', samples[[i]], '\n')
}

plan(sequential)

furrr 与 purrr 的基准测试

并行化带来的收益取决于任务的计算量。对于简单操作(x^2),开销占主导地位,顺序执行反而更快。对于拟合许多模型等繁重任务,并行执行可以节省大量时间。

library(furrr)
library(purrr)
plan(multisession, workers = 4)

# Heavy task: bootstrap a linear model 100 times
heavy <- function(i) {
  n <- 200
  df <- data.frame(x = rnorm(n), y = rnorm(n))
  coef(lm(y ~ x, data = df))[['x']]
}

seq_time <- system.time(map_dbl(1:40, heavy))[['elapsed']]
par_time <- system.time(
  future_map_dbl(1:40, heavy, .options = furrr_options(seed = TRUE))
)[['elapsed']]

cat('Sequential:', round(seq_time, 2), 's\n')
cat('Parallel:  ', round(par_time, 2), 's\n')
cat('Speedup:   ', round(seq_time / max(par_time, 0.001), 2), 'x\n')

plan(sequential)

future_map 中的错误处理

如果任一元素的计算抛出错误,future_map() 会停止并再次抛出该错误。若要在出现错误时继续运行,请在函数外使用 purrr::safely() 或 purrr::possibly() 包装器。

library(furrr)
library(purrr)
plan(multisession, workers = 2)

# Wrap with safely() to capture errors as results
safe_log <- safely(log, otherwise = NA_real_)

inputs <- list(10, -1, 100, 'text', 0.5)
results <- future_map(inputs, safe_log)

for (i in seq_along(results)) {
  if (is.null(results[[i]]$error)) {
    cat('Input', i, '-> result:', round(results[[i]]$result, 4), '\n')
  } else {
    cat('Input', i, '-> error:', conditionMessage(results[[i]]$error), '\n')
  }
}

plan(sequential)

实用的 furrr 处理流程

下面是一个完整的端到端处理流程:加载数据,使用可复现的随机种子并行拟合多个模型,提取性能指标,然后选出最佳模型——全部采用 furrr 风格。

library(furrr)
library(purrr)
plan(multisession, workers = 4)

set.seed(1)
n <- 300
df <- data.frame(
  x1 = rnorm(n), x2 = rnorm(n), x3 = rnorm(n),
  y  = rnorm(n)
)

formulas <- list(
  y ~ x1,
  y ~ x1 + x2,
  y ~ x1 + x2 + x3,
  y ~ x1 * x2
)

# Fit all models in parallel
models <- future_map(
  formulas,
  ~lm(.x, data = df),
  .options = furrr_options(seed = FALSE)
)

# Extract adjusted R-squared
adj_r2 <- map_dbl(models, ~summary(.x)$adj.r.squared)
cat('Adjusted R2 per model:',
    paste(round(adj_r2, 4), collapse = ', '), '\n')
cat('Best model:', which.max(adj_r2), '\n')

plan(sequential)

快速检查

您希望在并行调用 future_map() 时生成可复现的随机数。应该设置 furrr_options() 中的哪个选项?

回顾:furrr 包

要点:

  • furrr 是 purrr 的可直接替换的并行版本——只需将 map 替换为 future_map
  • 首先使用 plan(multisession, workers = n) 设置后端
  • 类型变体包括:future_map_dbl()、future_map_int()、future_map_chr()
  • 使用 future_map2() 和 future_pmap() 进行多输入并行映射
  • 使用 furrr_options(seed = 42L) 生成可复现的并行随机数
  • 集成 progressr,在长时间并行任务期间显示进度条
  • 在 future_map() 中使用 purrr::safely(),构建能够从错误中恢复的处理流程
library(furrr)
plan(multisession, workers = 2)

results <- future_map_dbl(
  1:6,
  ~.x^2 + sqrt(.x),
  .options = furrr_options(seed = TRUE)
)
cat(round(results, 3), '\n')

plan(sequential)

常见问题解答

「furrr:并行执行 purrr 操作」课时是免费的吗?

是的 — 「furrr:并行执行 purrr 操作」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 R Academy 课程的其余内容,请升级到 CoddyKit PRO。 R Academy 课程共包含 4 节课。

「furrr:并行执行 purrr 操作」这节课中我会学到什么?

使用 future_map() 直接替换 map(),立即实现并行化 你通过在浏览器中直接运行的动手代码来练习 R Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 R Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 R Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「furrr:并行执行 purrr 操作」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 R Academy 课中编写并运行代码吗?

能。每节 R Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. parallel 软件包与 detectCores()
  2. future 框架
  3. furrr:并行执行 purrr 操作
  4. 调试并行代码与负载均衡
← 返回 R Academy