【问题标题】:Increase F# map insertion performance提高 F# 地图插入性能
【发布时间】:2020-05-08 19:04:28
【问题描述】:

我目前正在对 F# 映射与 C# 字典进行一些测试。我意识到它们在实现方面完全不同,但它们确实为各自的语言提供了相同的用途。

我设计了一个简单的测试来检查插入时间,因为 F# 映射是不可变的,因此它必须为每次插入创建一个全新的映射。我想知道这有多大的成功。

测试如下:

 //F# 
 module Test = 
    let testMapInsert () = 
        let sw = Stopwatch()
        let rec fillMap endIdx curr map =
            if curr = endIdx then 
                map 
            else 
                fillMap endIdx (curr + 1) (map |> Map.add curr curr)
        sw.Start ()
        let q = fillMap 100000000 Map.empty
        sw.Stop ()
        printfn "%A" sw.ElapsedMilliseconds

 //C#
 class Program
    {
        static void Test(int x) {
            var d = new Dictionary<int,int>();
            for (int i = 0; i < x; i++) {
                d.Add(i,i);
            }
        }
        static void Main(string[] args) {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            Test(10000000);
            sw.Stop();
            System.Console.WriteLine(sw.ElapsedMilliseconds);
            //FSHARP.Test.testMapInsert(); f# function called in c#.

        }
    }

使用它进行 1000 万次元素插入会产生以下以 ms 为单位的时间:

C#: 332

F#: 13605

我认为 C# 字典会快一些,但差别很大。

有没有办法为这种用例加快 F# 字典的速度?或者这就是这种情况,F# 映射在这些情况下为了线程安全而与性能进行权衡?

【问题讨论】:

  • Just F# Map 是一种基于红/黑树的不可变数据结构,与 C# 中基于哈希表的可变字典相比。人们确实希望这会产生一些性能差异,但根据我的经验,F# Map 的最大差异来自于缓慢的通用比较函数。它很慢,因为它是通用的。我正在试验一个允许自定义通用比较函数的地图实现,但它的效果还不够好,无法对 F# 核心进行 PR。

标签: c# .net dictionary f#


【解决方案1】:

正如 cmets 中提到的,区别不是基于 C# 和 F# 之间的区别,而是基于不可变基于树的映射和基于哈希表的可变字典之间的区别。

使用#time,我在 F# 交互中得到以下性能:

#time 
// Immutable tree-based F# map (~14 sec)
let mutable map = Map.empty
for i in 0 .. 10000000 do
  map <- Map.add i i map

// Mutable hashtable-based .NET dictionary (~0.3 sec)
let dict = System.Collections.Generic.Dictionary<_, _>()
for i in 0 .. 10000000 do
  dict.Add(i, i)

有趣的问题是——你能让不可变的 F# 映射更快吗?原则上,如果您知道您正在使用已排序的数组,则可以更快地构建地图。 F# 映射没有任何可以让您执行此操作的操作,但可以添加。

当我定义我自己的与 F# 映射共享内部结构的 Map 类型时:

type MapTree<'Key, 'Value when 'Key : comparison > = 
  | MapEmpty 
  | MapOne of 'Key * 'Value
  | MapNode of 'Key * 'Value * MapTree<'Key, 'Value> *  MapTree<'Key, 'Value> * int

然后我可以定义ofSortedArray操作:

let height = function
  | MapEmpty -> 0
  | MapOne _ -> 1
  | MapNode(_, _, _, _, h) -> h

let rec ofSortedArray (data:_[]) i j = 
  if i = j then MapOne(data.[i])
  elif i > j then MapEmpty 
  else 
    let m = i + (j - i) / 2
    let l, r = ofSortedArray data i (m - 1), ofSortedArray data (m + 1) j
    let k, v = data.[m]
    MapNode(k, v, l, r, 1 + (max (height l) (height r)))

这仍然远不及可变哈希表的效率,但我得到以下信息:

// Immutable tree-based F# map, using sorted array 
let arr = [| for i in 0 .. 10000000 -> i, i |] // ~1 sec
let map = ofSortedArray arr 0 10000000         // ~3 sec

如果您真的想使用它,则需要您自己的 F# 映射版本 - 或者您可以向 F# 核心库发送拉取请求,添加对此类内容的支持!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-01-05
    • 2021-08-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-31
    • 1970-01-01
    相关资源
    最近更新 更多