Skip to content

Commit

Permalink
Map: optimize comparer: add floats & IComparable support
Browse files Browse the repository at this point in the history
  • Loading branch information
buybackoff committed Jan 9, 2021
1 parent 0e217e2 commit 8f1351c
Showing 1 changed file with 42 additions and 26 deletions.
68 changes: 42 additions & 26 deletions src/fsharp/FSharp.Core/map.fs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
#nowarn "1182"
namespace Microsoft.FSharp.Collections

open System
Expand Down Expand Up @@ -36,39 +35,42 @@ module MapTree =
type CompareHelper<'T when 'T : comparison>() =
static let c = LanguagePrimitives.FastGenericComparer

static member private IsIComparableFunc() =

match box(Unchecked.defaultof<'T>) with
| :? IComparable<'T> -> true
| _ -> false

static member private CompareC<'U when 'U :> IComparable<'U>>(l:'U, r:'U):int = l.CompareTo(r)
static member val Comparer: IComparer<'T> = c

static member val CompareToDlg =
// A constrained call to IComparable<'T>.CompareTo
static member private CompareCG<'U when 'U :> IComparable<'U>>(l:'U, r:'U):int = l.CompareTo(r)

// A call to IComparable.CompareTo
static member private CompareC<'U when 'U :> IComparable>(l:'U, r:'U):int = l.CompareTo(r)

static member val CompareToDlg : Func<'T,'T,int> =
let dlg =
try
if CompareHelper<'T>.IsIComparableFunc() then
// See #816, IComparable<'T> actually does not satisfy comparison constraint, but it should be preferred
if typeof<IComparable<'T>>.IsAssignableFrom(typeof<'T>) then
let m =
typeof<CompareHelper<'T>>.GetMethod("CompareCG", BindingFlags.NonPublic ||| BindingFlags.Static)
.MakeGenericMethod([|typeof<'T>|])
Delegate.CreateDelegate(typeof<Func<'T,'T,int>>, m) :?> Func<'T,'T,int>
elif typeof<IComparable>.IsAssignableFrom(typeof<'T>) then
let m =
typeof<CompareHelper<'T>>.GetMethod("CompareC", BindingFlags.NonPublic ||| BindingFlags.Static)
.MakeGenericMethod([|typeof<'T>|])
let dlg = Delegate.CreateDelegate(typeof<Func<'T,'T,int>>, m) :?> Func<'T,'T,int>
dlg
else
null
Delegate.CreateDelegate(typeof<Func<'T,'T,int>>, m) :?> Func<'T,'T,int>
else null
with _ -> null
dlg
with get

// If backed by static readonly field that will be JIT-time constant (must ensure beforefieldinit)
// If backed by static readonly field that will be JIT-time constant
static member val IsIComparable = not(isNull CompareHelper<'T>.CompareToDlg) with get

[<MethodImpl(MethodImplOptions.AggressiveInlining)>]
static member Compare(l:'T, r:'T):int =
if CompareHelper<'T>.IsIComparable then
CompareHelper<'T>.CompareToDlg.Invoke(l,r)
else
// Should use IsIComparable when it's backed by static readonly field
if isNull CompareHelper<'T>.CompareToDlg then
c.Compare(l, r)

else
CompareHelper<'T>.CompareToDlg.Invoke(l,r)

// Constructors are not inlined by F#, but JIT could inline them.
// This is what we need here, because LanguagePrimitives.FastGenericComparer.Compare
// has a .tail prefix that breaks the typeof(T)==typeof(...) JIT optimization in cmp
Expand All @@ -90,24 +92,38 @@ module MapTree =
else if Type.op_Equality(typeof<'T>, typeof<int16>) then unbox<int16>(box(l)).CompareTo(unbox<int16>(box(r)))
else if Type.op_Equality(typeof<'T>, typeof<int32>) then unbox<int32>(box(l)).CompareTo(unbox<int32>(box(r)))
else if Type.op_Equality(typeof<'T>, typeof<int64>) then unbox<int64>(box(l)).CompareTo(unbox<int64>(box(r)))

else if Type.op_Equality(typeof<'T>, typeof<byte>) then unbox<byte>(box(l)).CompareTo(unbox<byte>(box(r)))
else if Type.op_Equality(typeof<'T>, typeof<uint16>) then unbox<uint16>(box(l)).CompareTo(unbox<uint16>(box(r)))
else if Type.op_Equality(typeof<'T>, typeof<uint32>) then unbox<uint32>(box(l)).CompareTo(unbox<uint32>(box(r)))
else if Type.op_Equality(typeof<'T>, typeof<uint64>) then unbox<uint64>(box(l)).CompareTo(unbox<uint64>(box(r)))

else if Type.op_Equality(typeof<'T>, typeof<nativeint>) then if (# "clt" l r : bool #) then (-1) else (# "cgt" l r : int #)
else if Type.op_Equality(typeof<'T>, typeof<unativeint>) then if (# "clt" l r : bool #) then (-1) else (# "cgt" l r : int #)
else if Type.op_Equality(typeof<'T>, typeof<bool>) then unbox<bool>(box(l)).CompareTo(unbox<bool>(box(r)))
else if Type.op_Equality(typeof<'T>, typeof<char>) then unbox<char>(box(l)).CompareTo(unbox<char>(box(r)))
else if Type.op_Equality(typeof<'T>, typeof<float>) then unbox<float>(box(l)).CompareTo(unbox<float>(box(r)))
else if Type.op_Equality(typeof<'T>, typeof<float32>) then unbox<float32>(box(l)).CompareTo(unbox<float32>(box(r)))

// F# rules for floats
else if Type.op_Equality(typeof<'T>, typeof<float>) then
if (# "clt" l r : bool #) then (-1)
elif (# "cgt" l r : bool #) then (1)
elif (# "ceq" l r : bool #) then (0)
elif (# "ceq" r r : bool #) then (-1)
else (# "ceq" l l : int #)
else if Type.op_Equality(typeof<'T>, typeof<float32>) then
if (# "clt" l r : bool #) then (-1)
elif (# "cgt" l r : bool #) then (1)
elif (# "ceq" l r : bool #) then (0)
elif (# "ceq" r r : bool #) then (-1)
else (# "ceq" l l : int #)
else if Type.op_Equality(typeof<'T>, typeof<decimal>) then unbox<decimal>(box(l)).CompareTo(unbox<decimal>(box(r)))
else if Type.op_Equality(typeof<'T>, typeof<DateTime>) then unbox<DateTime>(box(l)).CompareTo(unbox<DateTime>(box(r)))
else if Type.op_Equality(typeof<'T>, typeof<DateTimeOffset>) then unbox<DateTimeOffset>(box(l)).CompareTo(unbox<DateTimeOffset>(box(r)))
else if Type.op_Equality(typeof<'T>, typeof<TimeSpan>) then unbox<TimeSpan>(box(l)).CompareTo(unbox<TimeSpan>(box(r)))

else if Type.op_Equality(typeof<'T>, typeof<BigInteger>) then unbox<BigInteger>(box(l)).CompareTo(unbox<BigInteger>(box(r)))
else if Type.op_Equality(typeof<'T>, typeof<string>) then Unchecked.compare (unbox<string>(box(l))) (unbox<string>(box(r)))

else if Type.op_Equality(typeof<'T>, typeof<string>) then
// same as in GenericComparisonFast
String.CompareOrdinal(unbox<string>(box(l)),(unbox<string>(box(r))))

else Comparison(l,r).Value

Expand Down

0 comments on commit 8f1351c

Please sign in to comment.