Equality operator causes boxing on value types
See original GitHub issueComparing value types with the equality operator (=) causes unnecessary boxing to occur. Tested on Visual Studio 2015 RC. I described the issue in this StackOverflow question.
Example:
[<Struct>]
type MyVal =
val X : int
new(x) = { X = x }
for i in 0 .. 10000000 do
(MyVal(i) = MyVal(i + 1)) |> ignore;;
Real: 00:00:00.077, CPU: 00:00:00.078, GC gen0: 38, gen1: 1, gen2: 0
My totally uninformed guess here is that the type is casted to IEquatable<_> before the strongly typed Equals is then called.
But it gets worse! If custom equality is defined:
[<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
for i in 0 .. 10000000 do
(MyVal(i) = MyVal(i + 1)) |> ignore;;
Real: 00:00:00.497, CPU: 00:00:00.500, GC gen0: 77, gen1: 1, gen2: 0
Then the equality operator calls Equals(obj)
, boxing both operands in the process! Nevermind the casts then required. As you can see this results in roughly twice the amount of GC pressure.
Even for reference types, this is suboptimal because casts are required.
The only workaround this problem is systematically avoid use of this operator, which may not be feasible when using generic code in third-party F# libraries. I see no reason why the compiler shouldn’t generate a direct call to IEquatable<T>.Equals
without boxing or casts; a call that could then be properly inlined by the CLR in many cases.
Issue Analytics
- State:
- Created 8 years ago
- Reactions:10
- Comments:40 (34 by maintainers)
Top GitHub Comments
I disagree that this should resolved as by design. This is surprising behavior and a performance trap. It affects all library code that uses this operator or structural equality, like Array.contains, Array.except, Array.distinct (and the same in Seq, List); causing N allocations for an input of size N is not a small performance problem, especially for larger value types (for ex. a 4x4 matrix of floats). There is no way to know which functions to avoid except through benchmarking. This reduces F#'s appeal in low-latency or high performance applications.
@christian-clausen
It is a shame. It’s not just
=
though, it’s usage through containers, to members in records - especially then you have to consider generics. It’s quite nasty. So making a warning would be difficult (impossible?) to cover all usages; so would it really be a good warning? (Maybe some code analyser could at least offer some hints, albeit in a limited form)I’ve tried, and tried to get something suitable to fix this but I just didn’t get into the fsharp early enough in it’s lifecycle, and now any change is going to be a breaking one - it appears that it’s unacceptable to prefer
IEquatable<>.Equals(...)
overobject.Equals(...)
and technically this is a breaking change. But sad.Anyway, it has lost me as an fsharp developer, which is a shame as I really love writing code in fsharp, but with advancements in csharp over the years, it’s, for the type of code I write, a lesser worse option. (I mean most people probably just don’t care about performance at this level, so it’s mostly a non-issue, which is why it’s never found enough traction to solve, and I understand that. But does make me sad.)