Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Faster Integer Range Operators #931

Merged
merged 9 commits into from
Jun 7, 2016
141 changes: 141 additions & 0 deletions src/fsharp/FSharp.Core.Unittests/FSharp.Core/PrimTypes.fs
Original file line number Diff line number Diff line change
Expand Up @@ -683,3 +683,144 @@ type UnboxAndOptionStuff() =
Assert.IsTrue( not (isNull [| |]))
Assert.IsTrue( not (isNull ""))
Assert.IsTrue( not (isNull "1"))


module internal RangeTestsHelpers =
// strictly speaking, this is just undefined behaviour, but at some point the F# library decided that
// it was an exception, so we are ensuring that such behaviour is retained
let inline regressionExceptionBeforeStartSingleStepRangeEnumerator zero one =
let sequence = seq { zero .. one .. one }
let enumerator = sequence.GetEnumerator()
enumerator.Current |> ignore

let inline regressionExceptionBeforeStartVariableStepIntegralRange zero two =
let sequence = seq { zero .. two .. two }
let enumerator = sequence.GetEnumerator()
enumerator.Current |> ignore

// strictly speaking, this is just undefined behaviour, but at some point the F# library decided that
// it was an exception, so we are ensuring that such behaviour is retained
let inline regressionExceptionAfterEndSingleStepRangeEnumerator zero one =
let sequence = seq { zero .. one .. one }
let enumerator = sequence.GetEnumerator()
while enumerator.MoveNext () do ignore ()
enumerator.Current |> ignore

let inline regressionExceptionAfterEndVariableStepIntegralRange zero two =
let sequence = seq { zero .. two .. two }
let enumerator = sequence.GetEnumerator()
while enumerator.MoveNext () do ignore ()
enumerator.Current |> ignore

let inline exceptions zero one two =
Assert.Throws (typeof<System.ArgumentException>, (fun () -> [one .. zero .. two] |> List.length |> ignore)) |> ignore

Assert.Throws (typeof<System.InvalidOperationException>, (fun () -> regressionExceptionBeforeStartSingleStepRangeEnumerator zero one)) |> ignore
Assert.Throws (typeof<System.InvalidOperationException>, (fun () -> regressionExceptionBeforeStartVariableStepIntegralRange zero two)) |> ignore
Assert.Throws (typeof<System.InvalidOperationException>, (fun () -> regressionExceptionAfterEndSingleStepRangeEnumerator zero one)) |> ignore
Assert.Throws (typeof<System.InvalidOperationException>, (fun () -> regressionExceptionAfterEndVariableStepIntegralRange zero two)) |> ignore

let inline common (min0, min1, min2, min3) (max0, max1, max2, max3) (zero, one, two, three) =
Assert.AreEqual ([min0 .. min3], [min0; min1; min2; min3])
Assert.AreEqual ([min0 .. one .. min3], [min0; min1; min2; min3])
Assert.AreEqual ([min0 .. two .. min3], [min0; min2])
Assert.AreEqual ([min0 .. three .. min3], [min0; min3])

Assert.AreEqual ([max3 .. max0], [max3; max2; max1; max0])
Assert.AreEqual ([max3 .. one .. max0], [max3; max2; max1; max0])
Assert.AreEqual ([max3 .. two .. max0], [max3; max1])
Assert.AreEqual ([max3 .. three .. max0], [max3; max0])

Assert.AreEqual ([max0 .. min0], [])
Assert.AreEqual ([max0 .. one .. min0], [])
Assert.AreEqual ([max0 .. two .. min0], [])
Assert.AreEqual ([max0 .. three .. min0], [])

exceptions zero one two

// tests for singleStepRangeEnumerator, as it only is used if start and/or end are not the
// minimum or maximum of the number range and it is counting by 1s
Assert.AreEqual ([min1 .. min3], [min1; min2; min3])
Assert.AreEqual ([max3 .. max1], [max3; max2; max1])

let inline signed min0 max0 =
let zero = LanguagePrimitives.GenericZero
let one = LanguagePrimitives.GenericOne
let two = one + one
let three = two + one

let min1 = min0 + one
let min2 = min1 + one
let min3 = min2 + one

let max1 = max0 - one
let max2 = max1 - one
let max3 = max2 - one

common (min0, min1, min2, min3) (max0, max1, max2, max3) (zero, one, two, three)

Assert.AreEqual ([min0 .. max0 .. max0], [ min0; min0 + max0; min0 + max0 + max0 ])
Assert.AreEqual ([min0 .. max1 .. max0], [ min0; min0 + max1; min0 + max1 + max1 ])
Assert.AreEqual ([min0 .. max2 .. max0], [ min0; min0 + max2; min0 + max2 + max2 ])
Assert.AreEqual ([min0 .. max3 .. max0], [ min0; min0 + max3; min0 + max3 + max3 ])

Assert.AreEqual ([min3 .. -one .. min0], [min3; min2; min1; min0])
Assert.AreEqual ([min3 .. -two .. min0], [min3; min1])
Assert.AreEqual ([min3 .. -three .. min0], [min3; min0])

Assert.AreEqual ([max0 .. -one .. max3], [max0; max1; max2; max3])
Assert.AreEqual ([max0 .. -two .. max3], [max0; max2])
Assert.AreEqual ([max0 .. -three .. max3], [max0; max3])

Assert.AreEqual ([min0 .. -one .. max0], [])
Assert.AreEqual ([min0 .. -two .. max0], [])
Assert.AreEqual ([min0 .. -three .. max0], [])

Assert.AreEqual ([max0 .. min0 .. min0], [max0; max0 + min0])
Assert.AreEqual ([max0 .. min1 .. min0], [max0; max0 + min1; max0 + min1 + min1 ])
Assert.AreEqual ([max0 .. min2 .. min0], [max0; max0 + min2; max0 + min2 + min2 ])
Assert.AreEqual ([max0 .. min3 .. min0], [max0; max0 + min3; max0 + min3 + min3 ])

let inline unsigned min0 max0 =
let zero = LanguagePrimitives.GenericZero
let one = LanguagePrimitives.GenericOne
let two = one + one
let three = two + one

let min1 = min0 + one
let min2 = min1 + one
let min3 = min2 + one

let max1 = max0 - one
let max2 = max1 - one
let max3 = max2 - one

common (min0, min1, min2, min3) (max0, max1, max2, max3) (zero, one, two, three)

Assert.AreEqual ([min0 .. max0 .. max0], [min0; min0 + max0])
Assert.AreEqual ([min0 .. max1 .. max0], [min0; min0 + max1])
Assert.AreEqual ([min0 .. max2 .. max0], [min0; min0 + max2])
Assert.AreEqual ([min0 .. max3 .. max0], [min0; min0 + max3])

[<TestFixture>]
type RangeTests() =
[<Test>] member __.``Range.SByte`` () = RangeTestsHelpers.signed System.SByte.MinValue System.SByte.MaxValue
[<Test>] member __.``Range.Byte`` () = RangeTestsHelpers.unsigned System.Byte.MinValue System.Byte.MaxValue
[<Test>] member __.``Range.Int16`` () = RangeTestsHelpers.signed System.Int16.MinValue System.Int16.MaxValue
[<Test>] member __.``Range.UInt16`` () = RangeTestsHelpers.unsigned System.UInt16.MinValue System.UInt16.MaxValue
[<Test>] member __.``Range.Int32`` () = RangeTestsHelpers.signed System.Int32.MinValue System.Int32.MaxValue
[<Test>] member __.``Range.UInt32`` () = RangeTestsHelpers.unsigned System.UInt32.MinValue System.UInt32.MaxValue
[<Test>] member __.``Range.Int64`` () = RangeTestsHelpers.signed System.Int64.MinValue System.Int64.MaxValue
[<Test>] member __.``Range.UInt64`` () = RangeTestsHelpers.unsigned System.UInt64.MinValue System.UInt64.MaxValue

[<Test>]
member __.``Range.IntPtr`` () =
if System.IntPtr.Size >= 4 then RangeTestsHelpers.signed (System.IntPtr System.Int32.MinValue) (System.IntPtr System.Int32.MaxValue)
if System.IntPtr.Size >= 8 then RangeTestsHelpers.signed (System.IntPtr System.Int64.MinValue) (System.IntPtr System.Int64.MaxValue)

[<Test>]
member __.``Range.UIntPtr`` () =
if System.UIntPtr.Size >= 4 then RangeTestsHelpers.unsigned (System.UIntPtr System.UInt32.MinValue) (System.UIntPtr System.UInt32.MaxValue)
if System.UIntPtr.Size >= 8 then RangeTestsHelpers.unsigned (System.UIntPtr System.UInt64.MinValue) (System.UIntPtr System.UInt64.MaxValue)


128 changes: 118 additions & 10 deletions src/fsharp/FSharp.Core/prim-types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6497,6 +6497,114 @@ namespace Microsoft.FSharp.Core
interface IEnumerable with
member x.GetEnumerator() = (gen() :> IEnumerator) }

[<NoEquality; NoComparison>]
type VariableStepIntegralRangeState<'T> = {
mutable Started : bool
mutable Complete : bool
mutable Current : 'T
}
let inline variableStepIntegralRange n step m =
if step = LanguagePrimitives.GenericZero then
invalidArg "step" (SR.GetString(SR.stepCannotBeZero));

let variableStepRangeEnumerator () =
let state = {
Started = false
Complete = false
Current = Unchecked.defaultof<'T>
}

let current () =
// according to IEnumerator<int>.Current documentation, the result of of Current
// is undefined prior to the first call of MoveNext and post called to MoveNext
// that return false (see https://msdn.microsoft.com/en-us/library/58e146b7%28v=vs.110%29.aspx)
// so we should be able to just return value here, and we could get rid of the
// complete variable which would be faster
if not state.Started then
notStarted ()
elif state.Complete then
alreadyFinished ()
else
state.Current

{ new IEnumerator<'T> with
member __.Dispose () = ()

member __.Current = current ()

interface IEnumerator with
member __.Current = box (current ())

member __.Reset () =
state.Started <- false
state.Complete <- false
state.Current <- Unchecked.defaultof<_>

member __.MoveNext () =
if not state.Started then
state.Started <- true
state.Current <- n
state.Complete <-
( (step > LanguagePrimitives.GenericZero && state.Current > m)
|| (step < LanguagePrimitives.GenericZero && state.Current < m))
else
let next = state.Current + step
if (step > LanguagePrimitives.GenericZero && next > state.Current && next <= m)
|| (step < LanguagePrimitives.GenericZero && next < state.Current && next >= m) then
state.Current <- next
else
state.Complete <- true

not state.Complete}

{ new IEnumerable<'T> with
member __.GetEnumerator () = variableStepRangeEnumerator ()

interface IEnumerable with
member this.GetEnumerator () = (variableStepRangeEnumerator ()) :> IEnumerator }

let inline simpleIntegralRange minValue maxValue n step m =
if step <> LanguagePrimitives.GenericOne || n > m || n = minValue || m = maxValue then
variableStepIntegralRange n step m
else
// a constrained, common simple iterator that is fast.
let singleStepRangeEnumerator () =
let value : Ref<'T> = ref (n - LanguagePrimitives.GenericOne)

let inline current () =
// according to IEnumerator<int>.Current documentation, the result of of Current
// is undefined prior to the first call of MoveNext and post called to MoveNext
// that return false (see https://msdn.microsoft.com/en-us/library/58e146b7%28v=vs.110%29.aspx)
// so we should be able to just return value here, which would be faster
if !value < n then
notStarted ()
elif !value > m then
alreadyFinished ()
else
!value

{ new IEnumerator<'T> with
member __.Dispose () = ()
member __.Current = current ()

interface IEnumerator with
member __.Current = box (current ())
member __.Reset () = value := n - LanguagePrimitives.GenericOne
member __.MoveNext () =
if !value < m then
value := !value + LanguagePrimitives.GenericOne
true
elif !value = m then
value := m + LanguagePrimitives.GenericOne
false
else false }

{ new IEnumerable<'T> with
member __.GetEnumerator () = singleStepRangeEnumerator ()

interface IEnumerable with
member __.GetEnumerator () = (singleStepRangeEnumerator ()) :> IEnumerator }

// For RangeStepGeneric, zero and add are functions representing the static resolution of GenericZero and (+)
// for the particular static type.
let inline integralRangeStep<'T,'Step> (zero:'Step) (add:'T -> 'Step -> 'T) (n:'T, step:'Step, m:'T) =
Expand Down Expand Up @@ -6576,16 +6684,16 @@ namespace Microsoft.FSharp.Core
interface System.Collections.IEnumerable with
member x.GetEnumerator() = (gen() :> System.Collections.IEnumerator) }

let RangeInt32 n step m : seq<int> = integralRangeStep 0 (+) (n,step,m)
let RangeInt64 n step m : seq<int64> = integralRangeStep 0L (+) (n,step,m)
let RangeUInt64 n step m : seq<uint64> = integralRangeStep 0UL (+) (n,step,m)
let RangeUInt32 n step m : seq<uint32> = integralRangeStep 0ul (+) (n,step,m)
let RangeIntPtr n step m : seq<nativeint> = integralRangeStep 0n (+) (n,step,m)
let RangeUIntPtr n step m : seq<unativeint> = integralRangeStep 0un (+) (n,step,m)
let RangeInt16 n step m : seq<int16> = integralRangeStep 0s (+) (n,step,m)
let RangeUInt16 n step m : seq<uint16> = integralRangeStep 0us (+) (n,step,m)
let RangeSByte n step m : seq<sbyte> = integralRangeStep 0y (+) (n,step,m)
let RangeByte n step m : seq<byte> = integralRangeStep 0uy (+) (n,step,m)
let RangeInt32 n step m : seq<int> = simpleIntegralRange Int32.MinValue Int32.MaxValue n step m
let RangeInt64 n step m : seq<int64> = simpleIntegralRange Int64.MinValue Int64.MaxValue n step m
let RangeUInt64 n step m : seq<uint64> = simpleIntegralRange UInt64.MinValue UInt64.MaxValue n step m
let RangeUInt32 n step m : seq<uint32> = simpleIntegralRange UInt32.MinValue UInt32.MaxValue n step m
let RangeIntPtr n step m : seq<nativeint> = variableStepIntegralRange n step m
let RangeUIntPtr n step m : seq<unativeint> = variableStepIntegralRange n step m
let RangeInt16 n step m : seq<int16> = simpleIntegralRange Int16.MinValue Int16.MaxValue n step m
let RangeUInt16 n step m : seq<uint16> = simpleIntegralRange UInt16.MinValue UInt16.MaxValue n step m
let RangeSByte n step m : seq<sbyte> = simpleIntegralRange SByte.MinValue SByte.MaxValue n step m
let RangeByte n step m : seq<byte> = simpleIntegralRange Byte.MinValue Byte.MaxValue n step m
let RangeDouble n step m : seq<float> = floatingRange float (n,step,m)
let RangeSingle n step m : seq<float32> = floatingRange float32 (n,step,m)
let RangeGeneric one add n m : seq<'T> = integralRange (one,add,n,m)
Expand Down