如何将大型F#记录数组保存到文件中?

时间:2015-12-11 15:33:30

标签: f#

我想将一个大的f#记录数组(> 10,000,000个元素)保存到磁盘,以便以后可以很容易地将数组重新加载到内存中。我使用Visual F#2010中的以下简单函数进行技术计算:

let save filename x =
    use stream = new FileStream(filename, FileMode.Create)
    BinaryFormatter().Serialize(stream, x)

type Test = { a : int; b : int}

let x = [| for i in 1..6 do 
            let a=i
            let b=i*i
            yield {a=a;b=b}|]

save "file.dat" x

当我这样做时(使用真实数据)我收到错误:

System.Runtime.Serialization.SerializationException: The internal array cannot expand to greater than Int32.MaxValue elements.

现在,我的解决方案是转换为Deedle然后保存为csv,但我认为有一个更加计算效率的保存/重新加载选项,不需要从csv重建数组。

let x2 = x |> Frame.ofRecords
x2.SaveCsv("file.csv")

1 个答案:

答案 0 :(得分:2)

将10,000,000行写入文本文件不是问题。这是一个简单的演示:

> let lines = Seq.initInfinite (fun i -> sprintf "%i, %i, -%i" i (i * 2) i);;

val lines : seq<string>

> open System.IO;;
> #time;;

--> Timing now on

> File.WriteAllLines(@"test.csv", lines |> Seq.take 10000000);;
Real: 00:00:20.420, CPU: 00:00:20.343, GC gen0: 3528, gen1: 3, gen2: 1
val it : unit = ()

如您所见,这只需要20秒。

读回来的行也不错:

> let roundTripped = File.ReadLines @"test.csv";;
Real: 00:00:00.000, CPU: 00:00:00.000, GC gen0: 0, gen1: 0, gen2: 0

val roundTripped : System.Collections.Generic.IEnumerable<string>

正如您所看到的,这是瞬间发生的,因为roundTripped被加载为一个延迟评估的序列。

但是,可以枚举这些值:

> roundTripped |> Seq.iter (printfn "%s")

(为了清晰起见,打印输出被截断;实际上有1000万行。)

...
9999997, 19999994, -9999997
9999998, 19999996, -9999998
9999999, 19999998, -9999999
Real: 00:03:43.995, CPU: 00:01:15.390, GC gen0: 594, gen1: 23, gen2: 3
val it : unit = ()

这需要更长的时间,但我怀疑这主要是因为打印到控制台往往需要时间。

这些实验是在我3岁的联想X1 Carbon上进行的 - 这是一款相当主流的硬件。

因此,编写或阅读数百万条文本行没有问题,但请注意我已经避免使用数组来支持延迟评估的序列。

使用记录不会改变上述结论。我不敢在.NET序列化上设计任何类型的持久持久性解决方案(由于潜在的版本控制问题),所以我仍然会为此目的转换为其他格式。

坚持使用CSV:

type Test = { A : int; B : int }

let records = Seq.initInfinite (fun i -> { A = i; B = -i })
let csvs = records |> Seq.map (fun x -> sprintf "%i, %i" x.A x.B)

记录的编写和阅读时间与上述报告大致相同。