递归同步比递归异步更快

ebb*_*ebb 2 f#

为什么这Solution 2比效率更高Solution 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
Run Code Online (Sandbox Code Playgroud)

我本以为Solution 1会更快,因为它是异步的,我在并行运行它.我错过了什么?

Tom*_*cek 6

正如丹尼尔和布莱恩已经清楚地解释过的那样,你的解决方案可能是创造了太多短暂的异步计算(因此开销不仅仅是并行的收益).该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 }
Run Code Online (Sandbox Code Playgroud)

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

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

  • folderCollectorSync path需要1秒钟
  • folderCollector path 2需要花费600毫秒(结果类似于1到4之间的任何嵌套)