Map.ofSeq性能细分

时间:2015-08-06 11:52:08

标签: performance dictionary collections f#

虽然将450 MB / 6.6 Mio记录文件从Zip格式传输到内存中,因为序列可以提供足够的性能,但通过Map.ofSeq将该序列转换为地图似乎实际上是不可能的:

    // within a let binding ...
    getPart fileName xPath partUri
    |> fun x -> printfn " getPart finished, parsing ..." ; x |> Seq.map (fun x -> 
        let test name = 
            let x' = (xd x).Root.Descendants() |> Seq.filter (fun x'' -> x''
            // do some System.Xml.Linq stuff here)
    |> fun x -> printfn " Parsing finished,  reorg seq ..." ; x |> Seq.map (fun x -> x.Reference, x )
    |> fun x -> printfn " Reorg finished,  building cell map...   ### see this ###  " ; x |> Map.ofSeq

do printfn "%s" " ### never see this ### done, now building keys..."

这是一个已知问题还是我犯了错误?

2 个答案:

答案 0 :(得分:2)

正如@Petr在评论中提到的那样,Seq操作是懒惰的,因此您的中间消息会在任何工作完成之前立即打印出来。

也就是说,如果要创建一个非常大的查找表,那么Map.ofSeq比其他选项慢。这是一个使用100万个阵列的快速性能测试:

let rnd = System.Random()
let arr = Array.init 1000000 (fun i -> rnd.Next(), i) |> Seq.distinctBy fst |> Array.ofSeq

现在在我的机器上使用#time,我得到以下数字:

#time 
// 19 seconds
let m = Map.ofSeq arr

// 0.3 second
let d1 = dict arr

// 0.1 second
let d2 = System.Collections.Generic.Dictionary<int, int>(arr.Length)
for k, v in arr do d.Add(k, v)

主要区别在于地图是不可变的,因此您可以在保留地图原始值的同时添加和删除项目。如果这就是您所需要的,那么map就是最好的数据结构。

相反,使用dict创建一个只读字典(基于哈希表),可以快速查找,但一旦创建就无法修改。最后,第三个选项创建一个普通的可变哈希表。

Map.ofSeq慢的部分原因还在于当前实现逐个添加元素并在每次插入后重新平衡树(数据的存储方式)。这可以通过更聪明,更快速的方式完成(这对F#核心库来说是一个很好的贡献: - ))。

答案 1 :(得分:0)

我终于可以验证观察到的性能故障是由XML转换引起的。涉及的运行时间以小时计算。在重构XML转换后,我观察到dict经过的时间约为2秒,而我的数据集经历了Map约30秒。包括重构以引入基础数据记录的固定存储器布局。我怀疑这对性能有相关影响,但我没有单独衡量。