F#中的IEquatable,=运算符性能和结构相等
IEquatable in F#, = operator performance and structural equality
我想知道在哪些情况下 F# 中的相等性测试会导致装箱,以及是否存在覆盖 Equals
和 GetHashCode
并实现 IEquatable<>
比使用StructuralEqualityAttribute
。如果是这样,是否可以在不降低 =
运算符性能的情况下完成?
对于包含一个整数的简单结构,我 运行 一个重复相同相等性检查 1M 次的循环。我使用...
为循环计时
=
自定义(类型和值测试)相等:大约 110 毫秒
=
结构相等:20 毫秒到 25 毫秒
- 重定向到 IEquatable 的自定义
==
运算符:1 毫秒到 3 毫秒
- 直接比较值的自定义
==
运算符:0 毫秒(被优化器删除)
据我了解,IEquatable<>
接口可以用作性能优化,以防止在检查相等性时装箱。这在 C# 中似乎很常见,但我很难在 F# 中找到它的提及。此外,F# 编译器在尝试覆盖给定类型的 =
运算符时会报错。
StructuralEquality
属性 documented in the MSDN 仅覆盖 Equals
和 GetHashCode
。不过,它确实阻止了 IEquatable<>
的显式实现。但是,生成的类型与 IEquatable<MyType>
不兼容。这对我来说似乎不合逻辑,如果结构上等同的类型不实现 IEquatable<>
?
在 F# 规范(3.0 规范中的 8.15.6.2)中有关于 =
性能的说明,但我不知道该怎么做:
Note: In practice, fast (but semantically equivalent) code is emitted for direct calls to (=), compare,
and hash for all base types, and faster paths are used for comparing most arrays
之前给出的"base types"的定义对阅读这篇笔记似乎没什么用。这是指基本类型吗?
我很困惑。到底是怎么回事?如果类型可以用作集合键或用于频繁的相等性测试,那么正确的相等性实现会是什么样子?
以下是我根据有限的经验收集的内容:
However, the resulting type is incompatible with 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# 的预期相反。
I'm wondering in which cases equality tests in F# cause boxing
一个常见且令人烦恼的情况是对于定义在例如中的任何结构。 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。 (2022 年更新:尽管社区做出了英勇的努力,但这个问题从未得到解决)。
如果您认为明确转换为 IEquatable<T>
会有所帮助,那么它会有所帮助,但这本身就是一个装箱操作。但至少你可以通过这种方式节省自己的两个拳击之一。
I'm confused. What is going on? What would a proper equality
implementation look like, if the type might be used as a collection
key or in a frequent equality test?
我和你一样困惑。 F# 似乎非常支持 GC。即使是默认行为:
[<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# 中定义的任何使用此运算符的集合显然 运行 会出现装箱问题。
I'm wondering (...) whether there are cases in which overriding Equals
and GetHashCode and implementing IEquatable<> is preferable to using
the StructuralEqualityAttribute.
重点是定义您自己的自定义相等性,在这种情况下,您使用 CustomEqualityAttribute
而不是 StructuralEqualityAttribute
。
If so, can it be done without reducing the performance of the = operator?
更新: 我建议避免默认 (=) 并直接使用 IEquatable(T).Equals。您可以为此定义一个内联运算符,或者您甚至可以根据它重新定义 (=)。这对 F# 中的几乎所有类型都是正确的,对于其余类型,它不会编译,因此您不会 运行 陷入细微的错误。 (2022 年更新:我不确定这是个好主意。)
原文:
从 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
属性,以确保您不会意外使用性能不佳的默认值 =
.
我想知道在哪些情况下 F# 中的相等性测试会导致装箱,以及是否存在覆盖 Equals
和 GetHashCode
并实现 IEquatable<>
比使用StructuralEqualityAttribute
。如果是这样,是否可以在不降低 =
运算符性能的情况下完成?
对于包含一个整数的简单结构,我 运行 一个重复相同相等性检查 1M 次的循环。我使用...
为循环计时=
自定义(类型和值测试)相等:大约 110 毫秒=
结构相等:20 毫秒到 25 毫秒- 重定向到 IEquatable 的自定义
==
运算符:1 毫秒到 3 毫秒 - 直接比较值的自定义
==
运算符:0 毫秒(被优化器删除)
据我了解,IEquatable<>
接口可以用作性能优化,以防止在检查相等性时装箱。这在 C# 中似乎很常见,但我很难在 F# 中找到它的提及。此外,F# 编译器在尝试覆盖给定类型的 =
运算符时会报错。
StructuralEquality
属性 documented in the MSDN 仅覆盖 Equals
和 GetHashCode
。不过,它确实阻止了 IEquatable<>
的显式实现。但是,生成的类型与 IEquatable<MyType>
不兼容。这对我来说似乎不合逻辑,如果结构上等同的类型不实现 IEquatable<>
?
在 F# 规范(3.0 规范中的 8.15.6.2)中有关于 =
性能的说明,但我不知道该怎么做:
Note: In practice, fast (but semantically equivalent) code is emitted for direct calls to (=), compare, and hash for all base types, and faster paths are used for comparing most arrays
之前给出的"base types"的定义对阅读这篇笔记似乎没什么用。这是指基本类型吗?
我很困惑。到底是怎么回事?如果类型可以用作集合键或用于频繁的相等性测试,那么正确的相等性实现会是什么样子?
以下是我根据有限的经验收集的内容:
However, the resulting type is incompatible with
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# 的预期相反。
I'm wondering in which cases equality tests in F# cause boxing
一个常见且令人烦恼的情况是对于定义在例如中的任何结构。 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。 (2022 年更新:尽管社区做出了英勇的努力,但这个问题从未得到解决)。
如果您认为明确转换为 IEquatable<T>
会有所帮助,那么它会有所帮助,但这本身就是一个装箱操作。但至少你可以通过这种方式节省自己的两个拳击之一。
I'm confused. What is going on? What would a proper equality implementation look like, if the type might be used as a collection key or in a frequent equality test?
我和你一样困惑。 F# 似乎非常支持 GC。即使是默认行为:
[<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# 中定义的任何使用此运算符的集合显然 运行 会出现装箱问题。
I'm wondering (...) whether there are cases in which overriding Equals and GetHashCode and implementing IEquatable<> is preferable to using the StructuralEqualityAttribute.
重点是定义您自己的自定义相等性,在这种情况下,您使用 CustomEqualityAttribute
而不是 StructuralEqualityAttribute
。
If so, can it be done without reducing the performance of the = operator?
更新: 我建议避免默认 (=) 并直接使用 IEquatable(T).Equals。您可以为此定义一个内联运算符,或者您甚至可以根据它重新定义 (=)。这对 F# 中的几乎所有类型都是正确的,对于其余类型,它不会编译,因此您不会 运行 陷入细微的错误。 (2022 年更新:我不确定这是个好主意。)
原文: 从 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
属性,以确保您不会意外使用性能不佳的默认值 =
.