f# - 递归同步比递归异步更快

标签 f#

为什么解决方案 2解决方案 1 更有效?

(时间是100次运行的平均值,他们经过的文件夹总数是13217)

// Solution 1 (2608,9ms)
let rec folderCollector path =
  async { let! dirs = Directory.AsyncGetDirectories path 
          do! [for z in dirs -> folderCollector z] 
              |> Async.Parallel |> Async.Ignore }

// Solution 2 (2510,9ms)
let rec folderCollector path =
  let dirs = Directory.GetDirectories path 
  for z in dirs do folderCollector z

我原以为解决方案 1 会更快,因为它是异步的,而且我是并行运行的。我错过了什么?

最佳答案

正如 Daniel 和 Brian 已经清楚地解释过的,您的解决方案可能会创建太多短暂的异步计算(因此开销大于并行性带来的 yield )。 AsyncGetDirectories 操作也可能不是真正的非阻塞,因为它没有做太多工作。我在任何地方都看不到此操作的真正异步版本 - 它是如何定义的?

无论如何,使用普通的 GetDirectories,我尝试了以下版本(它只创建了少量的并行异步):

// Synchronous version
let rec folderCollectorSync path =
    let dirs = Directory.GetDirectories path 
    for z in dirs do folderCollectorSync z

// Asynchronous version that uses synchronous when 'nesting <= 0'
let rec folderCollector path nesting =
    async { if nesting <= 0 then return folderCollectorSync path 
            else let dirs = Directory.GetDirectories path 
                 do! [for z in dirs -> folderCollector z (nesting - 1) ] 
                     |> Async.Parallel |> Async.Ignore }

在一定数量的递归调用之后调用一个简单的同步版本是一个常见的技巧 - 它在并行化任何非常深的树状结构时使用。使用 folderCollector path 2,这将仅启动数十个并行任务(而不是数千个),因此效率会更高。

在我使用的示例目录(包含 4800 个子目录和 27000 个文件)中,我得到:

  • folderCollectorSync 路径 耗时 1 秒
  • folderCollector 路径 2 耗时 600 毫秒(1 和 4 之间的任何嵌套的结果都相似)

关于f# - 递归同步比递归异步更快,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7085131/

相关文章:

F#接口(interface)继承失败,由于单元

F# 源文件的编译器顺序

f# - 如何声明可为空的受歧视联合?

f# - 如何插入代码质量指标 - FAKE F#MAKE

mysql - 如何将 F# 连接到 MySQL 数据库?

f# - 使用 F# 下载 Windows Phone 应用评论

f# - 匹配一个数字,如果它是 2 的倍数

F# 显式成员约束 : The type variable ^T could not be generalized because it would escape its scope

f# - 如何将 app.config 用于带有 FSharp.Data.TypeProviders 的数据库连接字符串?

wpf - F#dispatcher.invoke 和委托(delegate)方法