IE中的IEquatable,=运算符性能和结构相等

时间:2015-01-25 23:07:42

标签: f# equality

我想知道F#中的等式测试在哪些情况下导致装箱,以及是否存在覆盖EqualsGetHashCode以及实施IEquatable<>优于使用{{}的情况1}}。如果是这样,是否可以在不降低StructuralEqualityAttribute运算符性能的情况下完成?

对于包含一个整数的简单结构,我运行了一个循环,重复相同的等式检查1M次。我用循环定时循环...

  • =与自定义(类型和值测试)相等:约110毫秒
  • 结构平等的
  • =:20ms至25ms
  • 重定向到IEquatable的自定义=运算符:1ms到3ms
  • 直接比较值的自定义==运算符:0ms(由优化程序擦除)

根据我的理解,==接口可以用作性能优化,以在检查相等性时防止装箱。这似乎在C#中很常见,但我很难在F#中找到它。此外,当尝试覆盖给定类型的IEquatable<>运算符时,F#编译器会抱怨。

=属性为documented in the MSDN,仅覆盖StructuralEqualityEquals。但是,它确实阻止了GetHashCode的明确实施。但是,生成的类型与IEquatable<>不兼容。如果结构上等同的类型没有实现IEquatable<MyType>

,这对我来说似乎不合逻辑

F#规范中的IEquatable<>的性能有一个注释(3.0规范中的8.15.6.2),但我不知道该怎么做:

  

注意:在实践中,快速(但语义上等效)代码被发出以直接调用(=),比较,   和所有基类型的哈希值,更快的路径用于比较大多数数组

之前给出的“基本类型”的定义对于阅读本说明似乎没有用。这是指基本类型吗?

我很困惑。到底是怎么回事?如果类型可以用作集合键或频繁的相等测试,那么正确的等式实现会是什么样的?

1 个答案:

答案 0 :(得分:10)

根据我有限的经验,这是我收集的内容:

  

但是,结果类型与IEquatable<MyType>不兼容。

这是不正确的,结果类型确实实现了IEquatable<MyType>。您可以在ILDasm中进行验证。例如:

[<StructuralEquality;StructuralComparison>]    
type SomeType = {
    Value : int
}

let someTypeAsIEquatable = { Value = 3 } :> System.IEquatable<SomeType>
someTypeAsIEquatable.Equals({Value = 3}) |> ignore // calls Equals(SomeType) directly

也许你对F#没有像C#这样的隐式上传的方式感到困惑,所以如果你只是这样做:

{ Value = 3 }.Equals({Value = 4})

这实际上会调用Equals(obj)而不是接口成员,这与来自C#的期望相反。

  

我想知道在哪种情况下F#中的相等测试导致装箱

一个常见且令人烦恼的情况是在例如C#并实施IEquatable<T>,例如:

public struct Vector2f : IEquatable<Vector2f>

或类似地,F#中定义的任何结构,其自定义实现为IEquatable<T>,例如:

[<Struct;CustomEquality;NoComparison>]
type MyVal =
    val X : int
    new(x) = { X = x }
    override this.Equals(yobj) =
        match yobj with
        | :? MyVal as y -> y.X = this.X
        | _ -> false
    interface System.IEquatable<MyVal> with
        member this.Equals(other) =
            other.X = this.X

将此结构的两个实例与=运算符进行比较实际上会调用Equals(obj)而不是Equals(MyVal),导致在两个值进行比较时发生限制(和然后铸造和拆箱)。注意:我将此报告为bug on the Visualfsharp Github,显然这种情况应该早点修复。

如果你认为明确地向IEquatable<T>施放会有所帮助,那么它将会有所帮助,但它本身就是一个拳击行动。但至少你可以用这种方式保存自己的两个盒子中的一个。

  

我很困惑。到底是怎么回事?什么是适当的平等   如果类型可以用作集合,则实现看起来像   关键或频繁的平等测试?

我和你一样困惑。 F#看起来非常喜欢GC(不管它是GCs元组,并且不支持结构记录或DU)。甚至是默认行为:

[<Struct>]
type MyVal =
    val X : int
    new(x) = { X = x }

for i in 0 .. 1000000 do
      (MyVal(i) = MyVal(i + 1)) |> ignore;;
Réel : 00:00:00.008, Processeur : 00:00:00.015, GC gén0: 4, gén1: 1, gén2: 0

仍然导致拳击和过度的GC压力!请参阅下面的解决方法。

如果必须将该类型用作例如密钥,该怎么办?一本字典?好吧,如果它是System.Collections.Generics.Dictionary你很好,那就不使用F#相等运算符。但是在F#中使用此运算符定义的任何集合都会明显遇到拳击问题。

  

我想知道(...)是否存在重写等于的情况   和GetHashCode并实现IEquatable&lt;&gt;优于使用   StructuralEqualityAttribute。

重点是定义自己的自定义相等,在这种情况下,您使用CustomEqualityAttribute而不是StructuralEqualityAttribute

  

如果是这样,可以在不降低=运算符性能的情况下完成吗?

更新:我建议避免默认(=)并直接使用IEquatable(T).Equals。您可以为此定义内联运算符,或者您甚至可以根据它重新定义(=)。这对于F#中的几乎所有类型都是正确的,对于其余类型它不会编译,所以你不会遇到微妙的错误。 I describe the approach in detail here

<强>原始 从F#4.0开始,您可以执行以下操作(thanks latkin):

[<Struct>]
type MyVal =
    val X : int
    new(x) = { X = x }
    static member op_Equality(this : MyVal, other : MyVal) =
        this.X = other.X

module NonStructural =
    open NonStructuralComparison
    let test () =
        for i in 0 .. 10000000 do
              (MyVal(i) = MyVal(i + 1)) |> ignore

// Real: 00:00:00.003, CPU: 00:00:00.000, GC gen0: 0, gen1: 0, gen2: 0
NonStructural.test()

NonStructuralComparison模块使用只调用=的版本覆盖默认op_Equality。我会在结构中添加NoEqualityNoComparison属性,以确保您不会意外地使用效果不佳的默认=