为什么解决方案 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/