我想知道F#中的等式测试在哪些情况下导致装箱,以及是否存在覆盖Equals
和GetHashCode
以及实施IEquatable<>
优于使用{{}的情况1}}。如果是这样,是否可以在不降低StructuralEqualityAttribute
运算符性能的情况下完成?
对于包含一个整数的简单结构,我运行了一个循环,重复相同的等式检查1M次。我用循环定时循环...
=
与自定义(类型和值测试)相等:约110毫秒=
:20ms至25ms =
运算符:1ms到3ms ==
运算符:0ms(由优化程序擦除)根据我的理解,==
接口可以用作性能优化,以在检查相等性时防止装箱。这似乎在C#中很常见,但我很难在F#中找到它。此外,当尝试覆盖给定类型的IEquatable<>
运算符时,F#编译器会抱怨。
=
属性为documented in the MSDN,仅覆盖StructuralEquality
和Equals
。但是,它确实阻止了GetHashCode
的明确实施。但是,生成的类型与IEquatable<>
不兼容。如果结构上等同的类型没有实现IEquatable<MyType>
?
F#规范中的IEquatable<>
的性能有一个注释(3.0规范中的8.15.6.2),但我不知道该怎么做:
注意:在实践中,快速(但语义上等效)代码被发出以直接调用(=),比较, 和所有基类型的哈希值,更快的路径用于比较大多数数组
之前给出的“基本类型”的定义对于阅读本说明似乎没有用。这是指基本类型吗?
我很困惑。到底是怎么回事?如果类型可以用作集合键或频繁的相等测试,那么正确的等式实现会是什么样的?
答案 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
。我会在结构中添加NoEquality
和NoComparison
属性,以确保您不会意外地使用效果不佳的默认=
。