【问题标题】:Release the processed data in sequence依次释放处理后的数据
【发布时间】:2016-06-19 14:31:58
【问题描述】:

我正在使用 F# 进行数据处理。首先,我将所有文件放在一个目录中,然后处理每个文件以生成一些数据结构。最后,我会将处理后的数据存储到 SQLite 中。我知道,如果我使用 Seq 存储文件名,然后通过管道转发到 Seq.map,它将为每个文件执行惰性处理。但是有这么多文件在内存中包含所有这些文件是不可能的。然后在命令式编程语言中,我可以读取一个文件,处理它,存储它并释放中间数据并执行下一个文件。当然 F# 可以做命令式编程,但我想知道是否有机会以函数式编程风格来做?

files
|> Seq.map readFile
|> Seq.map processContent
|> Seq.map storeProcessResult

上面的代码显示了我的观点。 files 包含一系列文件名,然后我读取文件的内容,将其处理成某种结构,最后将结果存储到数据库中。我知道由于惰性行为,文件将被一一读取和处理。但最终数据何时公布?

【问题讨论】:

  • 您遇到错误了吗?你可以用递归来处理它。或者只是使用高阶函数,并使用use 打开文件。您可以轻松地处理将在数据库中填满 10 GB 的数据。
  • 那么为什么Seq 不适合你呢?正如你所说的,它很懒,如果Seq.map 操作读取文件,然后由Seq.fold 处理内容,那么你一次只能在内存中保留一个文件。
  • @FuleSnabel 你的意思是在Seq中,每一个处理过的数据都会被释放吗?
  • @s952163 这里没有错误。在我看来,seq 中的所有 yield 值都将保留在内存中,直到整个管道转发序列运行完毕。也许我在这一点上误解了。
  • 这是一个非常典型的工作流程,应该可以工作。您可能需要在其中构建一种方法,以确保释放 readfile 中的文件句柄,而不是保留对 ProcessContent 中数据的引用,并且很可能将 seq.iter 用于 storeProcessResult。 GC 会处理它。由于我们似乎在同一时区,请随时加入chat.stackoverflow.com/rooms/51909/f

标签: f# functional-programming lazy-evaluation


【解决方案1】:

显然只有您知道 readFile、processContent 和 storeProcessResult 函数内部发生了什么。正如@FuleSnabel 在他的评论中所说,您可以映射然后使用折叠(递归)来处理文件。

您可以执行一个简单的测试来查看内存消耗的差异:创建一个包含 1000 万个元素的列表的 List 并对列表求和,然后创建一个包含 1000 万个元素的列表的 Seq,并对列表进行求和。我使用的是 64 位 FSI。

这将使用大约 1GB 的内存:

let z = [for i in 1..3 -> List.init 10000000 (fun _ -> 1)]
let w = z  |> List.map (fun x -> System.GC.Collect();List.sum x)

这将只使用几 MB 的内存,甚至比一个包含 1000 万个 1 的列表要少得多:

let x = seq {for i in 1..3 -> List.init 10000000 (fun _ -> 1 ) }
let y = x |> Seq.map (fun x -> System.GC.Collect(); List.sum x)

这只是工作流程中的一个(而且可能很容易)部分。如果你要打开文件,你也必须确保关闭它们,因此我建议上面的use。但是我确实认识到访问文件系统并以惰性序列处理大量数据可能会导致一些问题,在这种情况下,您始终可以对其进行分析并查看瓶颈在哪里。

顺便说一句,你不需要在代码中直接调用 GC,我只是这样做了,中间结果不会污染测试中的内存计数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-07-02
    • 2020-04-24
    • 1970-01-01
    • 2023-03-18
    • 1970-01-01
    • 1970-01-01
    • 2015-05-31
    相关资源
    最近更新 更多