我想将一个大的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")
答案 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)
记录的编写和阅读时间与上述报告大致相同。