You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用future实现Shiny并行渲染反而更慢的原因咨询

Shiny中future并行耗时反而更长的原因分析

我分别测试了带future和不带future的Shiny示例,移除了10秒等待时间。理论上并行渲染应比非并行方案更快,但实际结果相反:带future的并行渲染耗时比非并行多1秒,我对此感到困惑,计划在大型应用中使用该机制但尚未理解其原理,恳请帮忙解释原因。

测试结果对比

  • 不带future的脚本reactlog:
    不带future的脚本reactlog
  • 带future的脚本reactlog:
    带future的脚本reactlog

测试代码

带future的脚本

library(shiny)
library(DT)
library(ggplot2)
library(data.table)
library(promises)
library(future)

plan(multisession)

ui <- navbarPage(
  tabPanel(
    "Новостные тренды"
    , sidebarLayout(
      sidebarPanel(
        br()
        , actionButton(
          "run_trends"
          , label = "run"
          , style="color: #fff; background-color: #337ab7; border-color: #2e6da4"
        )
        , br()
      )
      , mainPanel(
        textOutput("trends_time")
        , br()
        , br()
        , plotOutput('trend_plotly')
        , br()
        , p("results")
        , br()
        , DTOutput('trend_tbl')
        , br()
        , br()
      )
    )
  )
)

server <- function(input, output, session) {
  
  dt_trend <- eventReactive(
    input$run_trends,
    {
      dat_func <- function() {
        
        start_time <- Sys.time()
        dt <- data.table(x = rnorm(100), y = rnorm(100))
        trendy_tbl <- head(dt, 10)
        ggplo1 <- ggplot(dt) + geom_point(aes(x=x,y=y))
       
        list(
          trendy_tbl
          , ggplo1
          , paste0('time: ', round(Sys.time() - start_time), ' сек.')
        )
      }
      
      # Returning future
      future({
        dat_func()
      })
    })
  
  output$trend_tbl <- renderDT({dt_trend() %>% then(~.[[1]])})
  output$trend_plotly <- renderPlot({dt_trend() %>% then(~.[[2]])})
  output$trends_time <- renderText({dt_trend() %>% then(~.[[3]])})
  
}


shinyApp(ui, server)

不带future且运行更快的脚本

ui <- navbarPage(
  tabPanel(
    "Новостные тренды"
    , sidebarLayout(
      sidebarPanel(
        br()
        , actionButton(
          "run_trends"
          , label = "run"
          , style="color: #fff; background-color: #337ab7; border-color: #2e6da4"
        )
        , br()
      )
      , mainPanel(
        textOutput("trends_time")
        , br()
        , br()
        , plotOutput('trend_plotly')
        , br()
        , p("results")
        , br()
        , DTOutput('trend_tbl')
        , br()
        , br()
      )
    )
  )
)

server <- function(input, output, session) {

  observeEvent(
    input$run_trends,{
      start_time <- Sys.time()
      dt <- data.table(x = rnorm(100), y = rnorm(100))
      output$trend_tbl <- renderDT({head(dt, 10)})
      output$trend_plotly <- renderPlot({ggplot(dt) + geom_point(aes(x=x,y=y))})
      output$trends_time <- renderText({paste0('time: ', round(Sys.time() - start_time), ' сек.')})
    })
  
}

shinyApp(ui, server)

原因解释

  1. 多会话启动与环境复制开销
    使用plan(multisession)会启动新的R子进程,子进程需要初始化环境、加载依赖包(即使主进程已加载,子进程仍需复制或重新加载必要的对象和包),这部分开销对于当前的轻量任务(生成100行数据集、绘制简单散点图)来说,远大于并行计算能节省的时间。

  2. 未真正实现并行任务拆分
    带future的代码是把所有计算逻辑打包到同一个future中执行,并没有将独立任务(比如生成表格数据、绘制图表)拆分成多个并行的future。本质上只是把单块计算移到了子进程,反而增加了进程间数据传输的成本(计算结果从子进程传回主进程需要序列化/反序列化)。

  3. 非future版本的执行效率更高
    不带future的代码在主进程中一次性完成所有计算并直接绑定到输出,没有进程切换、通信的额外开销,对于轻量任务来说自然更快。future的优势仅在处理耗时较长的独立任务时才能体现——比如复杂数据清洗、机器学习模型训练,此时进程启动的固定开销会被并行节省的时间抵消。

优化建议

  • 如果要测试future的并行优势,需将独立任务拆分成多个future并行执行,例如:
    dt_future <- future({data.table(x = rnorm(100), y = rnorm(100))})
    plot_future <- future({ggplot(dt_future) + geom_point(aes(x=x,y=y))})
    
  • 在大型应用中,仅对耗时超过数秒的任务使用future,避免为轻量任务引入不必要的开销。
  • 可根据操作系统选择更轻量的future计划,比如Linux/macOS下使用plan(multicore)(无需完全启动新R进程,开销更低),Windows下可配合future::globals参数减少环境复制的内容。

内容的提问来源于stack exchange,提问作者akshay bholee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 16:54:16