3

在下面作为 .fsx 脚本执行的代码中,最后一行大约需要 30 秒才能完成。我假设由于记录是引用类型,最后一行只创建具有引用(不可变)大值的字段的记录,因此它应该非常快。为什么它很慢,我该如何解决?

type BigType = { Big: Set<int> }
type CollectionType = { BigVal: BigType; SmallVal: int }

let b = { Big = set [ 0 .. 999999 ] }
let mySet = set [ 0 .. 50 ]

#time

mySet |> Set.map (fun x -> { BigVal = b; SmallVal = x })

谢谢你。

4

4 回答 4

5

这里要注意的一件事是,您定义字段的顺序type CollectionType = { BigVal: BigType; SmallVal: int }会有所不同。如果你试试:

type BigType = { Big: Set<int> }
type CollectionType = { SmallVal: int; BigVal: BigType; }
let b = { Big = set [ 0 .. 999999 ] }
let mySet = set [ 0 .. 50 ]
#time
mySet |> Set.map (fun x -> { BigVal = b; SmallVal = x })

相反,所花费的时间从 Real: 00:00:34.385 变为 Real: 00:00:00.002。

注意:我最初担心这种行为不能被依赖并且可能会在没有警告的情况下改变;但是,nasosev 发现F# 语言规范中描述了这种行为,请参阅文档 4.1 版的第 8.15.3 节。

于 2021-01-11T10:53:53.637 回答
4

原因是它Set被实现为搜索树,因此为了将项目插入集合中,将其与一些现有项目进行比较。因此,确实只创建了少量记录,但正在比较整套记录。

在不知道您要解决的确切问题的情况下,很难说出解决问题的最佳方法是什么。但是,如果要求每个CollectionType值都具有不同的值SmallVal,那么您可以按照 Scott 的建议进行操作,并实现仅查看的自定义比较SmallVal。你不需要上课,你可以用记录来做:

(* For records, you need to put CustomComparison in addition to implementing IComparable.
   And CustomComparison requires CustomEquality and its corresponding overrides. *)
[<CustomComparison; CustomEquality>]
type CollectionType =
    { BigVal: BigType; SmallVal: int }

    interface System.IComparable with
        member this.CompareTo(that) =
            match that with
            | :? CollectionType as that -> compare this.SmallVal that.SmallVal
            | _ -> -1

    override this.Equals(that) =
        match that with
        | :? CollectionType as that -> this.SmallVal = that.SmallVal
        | _ -> false

    override this.GetHashCode() = this.SmallVal
于 2021-01-11T09:25:08.873 回答
3

如果先转换为数组,则根本不需要时间:

mySet |> Set.toList |> List.map (fun x -> { BigVal = b; SmallVal = x })

因此,创建记录所需的时间是微不足道的。它慢的原因是记录在一个集合内。作为实现的一部分,集合需要相互比较它们的项目,以确保没有重复的值。F# 通过比较记录的内容,在结构上比较记录。因此,正在比较的这些记录包含非常大的集合,需要很长时间才能进行比较。它们实际上是不同的记录,但就内存中的对象而言是相同的集合,但 F# 记录比较不知道这一点,也不会检查。

于 2021-01-11T09:21:04.563 回答
2

欢迎来到 F# 社区。

我猜每条新记录都在复制b,尽管由于记录默认是引用类型,正如你所说,我不确定为什么会这样。

这种方法并不快:

let y = { BigVal = b; SmallVal = 0 }

mySet |> Set.map (fun x -> { y with SmallVal = x })

相反,使用类要快得多:

type BigType = { Big: Set<int> }

let b = { Big = set [ 0 .. 999999 ] }

type CollectionType(smallVal: int) =
    interface System.IComparable with
        member __.CompareTo other =
            compare __.SmallVal (other :?> CollectionType).SmallVal

    member __.BigVal = b
    member __.SmallVal = smallVal

let mySet = set [ 0 .. 50 ]

#time

mySet |> Set.map (fun x -> CollectionType(x))

这不是一个完整的解决方案,因为有一个警告FS0343: The type 'CollectionType' implements 'System.IComparable' explicitly but provides no corresponding override for 'Object.Equals'. An implementation of 'Object.Equals' has been automatically provided, implemented via 'System.IComparable'. Consider implementing the override 'Object.Equals' explicitly

于 2021-01-11T07:05:28.940 回答