# Float64 comparison operator performance

**URL:** <https://discourse.julialang.org/t/float64-comparison-operator-performance/29179>\
**Category:** Performance\
**Created:** [September 26, 2019, 12:58am UTC](https://discourse.julialang.org/t/float64-comparison-operator-performance/29179 "2019-09-26T00:58:06Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![milesf](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milesf/32/9289_2.png) [@milesf](https://discourse.julialang.org/u/milesf)\
**Post date:** [September 26, 2019, 12:58am UTC](https://discourse.julialang.org/t/float64-comparison-operator-performance/29179/1 "2019-09-26T00:58:06Z")

</div>

I expect the performance of `a < b` to be the same as `Base.lt(Base.Forward, a, b)` if all type information is known at compile time, but I’m observing a 2.5x difference with `Float64`.

```julia
using BenchmarkTools
using Random

Random.seed!(0)
xs = rand(10^6)

function f1(a::Vector)
    n = length(a)
    c = 0
    for i = 2:n
        if a[i-1] < a[i]
        # Same as:
        #if Base.lt_float(a[i-1], a[i])
            c += 1
        end
    end
    return c
end

function f2(a::Vector{Float64})
    n = length(a)
    c = 0
    for i = 2:n
        if Base.lt(Base.Forward, a[i-1], a[i])
        # Same as:
        #if Base.fpislt(a[i-1], a[i])
            c += 1
        end
    end
    return c
end

julia> @benchmark f1($xs)
BenchmarkTools.Trial: 
  memory estimate: 0 bytes
  allocs estimate: 0
  --------------
  minimum time: 908.224 μs (0.00% GC)
  median time: 937.959 μs (0.00% GC)
  mean time: 951.230 μs (0.00% GC)
  maximum time: 1.760 ms (0.00% GC)
  --------------
  samples: 5229
  evals/sample: 1

julia> @benchmark f2($xs)
BenchmarkTools.Trial: 
  memory estimate: 0 bytes
  allocs estimate: 0
  --------------
  minimum time: 2.653 ms (0.00% GC)
  median time: 2.806 ms (0.00% GC)
  mean time: 2.831 ms (0.00% GC)
  maximum time: 4.744 ms (0.00% GC)
  --------------
  samples: 1763
  evals/sample: 1

julia> 

```

The root of the problem seem to be a difference of calling the faster `Base.lt_float` versus the slower `Base.fpislt`.  
[https://github.com/JuliaLang/julia/blob/9a1dbc038587c6072ac99699e416cbc8908054ed/base/float.jl#L458-L465](https://github.com/JuliaLang/julia/blob/9a1dbc038587c6072ac99699e416cbc8908054ed/base/float.jl#L458-L465)

```julia
julia> @code_native 1.0 < 2.0
    .text
; ┌ @ float.jl:452 within `<'
    vucomisd %xmm0, %xmm1
    seta %al
    retq
    nopl (%rax,%rax)
; └

julia> @code_native isless(1.0, 2.0)
    .text
; ┌ @ float.jl:459 within `isless'
    vmovq %xmm0, %rax
    vmovq %xmm1, %rcx
    testq %rax, %rax
    sets %dl
    setns %sil
    cmpq %rcx, %rax
    seta %al
    setl %cl
    andb %dl, %al
    andb %sil, %cl
    orb %al, %cl
    vucomisd %xmm1, %xmm0
    setnp %dl
    andb %cl, %dl
    vucomisd %xmm1, %xmm1
    setp %cl
    vucomisd %xmm0, %xmm0
    setnp %al
    andb %cl, %al
    orb %dl, %al
    retq
; └

```

What is the purpose of these two variants?

Also, it would be nice to look at the source of non-generic functions directly.

```julia
julia> @code_native Base.lt_float(1.0, 2.0)
ERROR: ArgumentError: argument is not a generic function

julia> @code_native Base.fpislt(1.0, 2.0)
ERROR: ArgumentError: argument is not a generic function

```

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [September 26, 2019, 1:04am UTC](https://discourse.julialang.org/t/float64-comparison-operator-performance/29179/2 "2019-09-26T01:04:11Z")

</div>

> [@milesf](#):
>
> What is the purpose of these two variants?

`Base.lt` is a total order, but `<` is not since all comparisons with NaN values return `false` according to the IEEE floating-point standard:

```julia
julia> Base.lt(Base.Forward, NaN, 1.0)
false

julia> Base.lt(Base.Forward, 1.0, NaN)
true

julia> NaN < 1.0
false

julia> 1.0 < NaN
false

```

---

<div class="post-metadata">

**Author:** ![milesf](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milesf/32/9289_2.png) [@milesf](https://discourse.julialang.org/u/milesf)\
**Post date:** [September 26, 2019, 1:23am UTC](https://discourse.julialang.org/t/float64-comparison-operator-performance/29179/3 "2019-09-26T01:23:41Z")

</div>

Is it possible to use the faster `<` version with the flexible ordering offered by `Base.lt` and `Base.Ordering`?  
I’m trying to make [DataStructures](https://github.com/JuliaCollections/DataStructures.jl/issues/243) behave more like sort, where users can drop in their own orderings but I don’t want to introduce a performance regression for the common case which does best with the existing hardcoded `<`.  
Thinking of something like the following, but I also want to make sure this is compatible with non-floats too.

```julia
Base.lt(CustomForwardFaster, a, b)

```

Is this a situation that would benefit from [@fastmath](https://docs.julialang.org/en/v1/base/math/#Base.FastMath.@fastmath)?

**Edit:**  
@fastmath does not improve benchmarking results.

**Edit 2:**

Here’s my workaround to disregard special NAN cases for floats, but still allow drop-in replacement by other `Base.Ordering` types:

```julia
struct FasterForward <: Base.Ordering end
Base.lt(o::FasterForward, a, b) = a < b

function f3(a::Vector{Float64})
    n = length(a)
    c = 0
    for i = 2:n
        if Base.lt(FasterForward(), a[i-1], a[i])
            c += 1
        end
    end
    return c
end

julia> @benchmark f3($xs)
BenchmarkTools.Trial: 
  memory estimate: 0 bytes
  allocs estimate: 0
  --------------
  minimum time: 908.760 μs (0.00% GC)
  median time: 921.672 μs (0.00% GC)
  mean time: 939.581 μs (0.00% GC)
  maximum time: 1.666 ms (0.00% GC)
  --------------
  samples: 5293
  evals/sample: 1

```

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [September 26, 2019, 6:15am UTC](https://discourse.julialang.org/t/float64-comparison-operator-performance/29179/4 "2019-09-26T06:15:25Z")

</div>

> [@milesf](#):
>
> Is it possible to use the faster `<` version with the flexible ordering offered by `Base.lt` and `Base.Ordering` ?

Why do you need to go through those constructs? Most APIs which need a comparison operator allow you to provide one.

---

<div class="post-metadata">

**Author:** ![milesf](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milesf/32/9289_2.png) [@milesf](https://discourse.julialang.org/u/milesf)\
**Post date:** [September 26, 2019, 7:01am UTC](https://discourse.julialang.org/t/float64-comparison-operator-performance/29179/5 "2019-09-26T07:01:54Z")

</div>

> [@Tamas\_Papp](#):
>
> Why do you need to go through those constructs? Most APIs which need a comparison operator allow you to provide one.

That’s a good question.

My understanding is that passing an ordering type seems to be more flexible than passing a comparison operator or function.

For example, lets say you have a custom struct, and you want to include some additional information about how to perform comparisons on these structs. These comparison rules might also change as the program is running.

You can easily include all of these comparison rules as data in a `Base.Ordering` struct. Here’s a [code snippet](https://discourse.julialang.org/t/structs-with-custom-ordering-in-a-heap/28753) describing that process.

The alternative is to convert this comparison rule data into a new comparison function each time the rules change. I can speculate on the downsides of this latter approach, but I haven’t tried it out yet. I assume `Base.Ordering` was provided as an alternative.

Maybe another reader could offer more concrete info about the origins and benefits of `Base.Ordering`.

---

<div class="post-metadata">

**Author:** ![milesf](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milesf/32/9289_2.png) [@milesf](https://discourse.julialang.org/u/milesf)\
**Post date:** [September 26, 2019, 7:16am UTC](https://discourse.julialang.org/t/float64-comparison-operator-performance/29179/6 "2019-09-26T07:16:23Z")

</div>

As another aside, it seems that using `FasterForward` (which achieved 2x performance in the earlier example) is slightly slower to sort 10^6 values, but still faster to sort 10^3 values.

Not sure what could explain this result.

```julia
using BenchmarkTools
using Random
Random.seed!(0)
xs = rand(10^6)
struct FasterForward <: Base.Ordering end
Base.lt(o::FasterForward, a, b) = a < b

julia> @benchmark sort!(v, order=ord) setup=(v = copy(xs); ord = Base.Forward)
BenchmarkTools.Trial: 
  memory estimate: 0 bytes
  allocs estimate: 0
  --------------
  minimum time: 69.552 ms (0.00% GC)
  median time: 69.888 ms (0.00% GC)
  mean time: 70.117 ms (0.00% GC)
  maximum time: 74.023 ms (0.00% GC)
  --------------
  samples: 68
  evals/sample: 1

julia> @benchmark sort!(v, order=ord) setup=(v = copy(xs); ord = FasterForward())
BenchmarkTools.Trial: 
  memory estimate: 0 bytes
  allocs estimate: 0
  --------------
  minimum time: 76.176 ms (0.00% GC)
  median time: 76.956 ms (0.00% GC)
  mean time: 78.676 ms (0.00% GC)
  maximum time: 99.118 ms (0.00% GC)
  --------------
  samples: 61
  evals/sample: 1

julia> xs = rand(10^3);

julia> @benchmark sort!(v, order=ord) setup=(v = copy(xs); ord = Base.Forward)
BenchmarkTools.Trial: 
  memory estimate: 0 bytes
  allocs estimate: 0
  --------------
  minimum time: 7.262 μs (0.00% GC)
  median time: 7.548 μs (0.00% GC)
  mean time: 7.672 μs (0.00% GC)
  maximum time: 12.796 μs (0.00% GC)
  --------------
  samples: 10000
  evals/sample: 7

julia> @benchmark sort!(v, order=ord) setup=(v = copy(xs); ord = FasterForward())
BenchmarkTools.Trial: 
  memory estimate: 0 bytes
  allocs estimate: 0
  --------------
  minimum time: 6.389 μs (0.00% GC)
  median time: 6.638 μs (0.00% GC)
  mean time: 6.809 μs (0.00% GC)
  maximum time: 14.075 μs (0.00% GC)
  --------------
  samples: 10000
  evals/sample: 7

```

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [September 26, 2019, 7:32am UTC](https://discourse.julialang.org/t/float64-comparison-operator-performance/29179/7 "2019-09-26T07:32:32Z")

</div>

AFAIK `Base.Ordering` is part of an internal API, see

[https://github.com/JuliaLang/julia/pull/22388#issuecomment-362380962](https://github.com/JuliaLang/julia/pull/22388#issuecomment-362380962)

I agree that it is confusing.

---

<div class="post-metadata">

**Author:** ![milesf](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milesf/32/9289_2.png) [@milesf](https://discourse.julialang.org/u/milesf)\
**Post date:** [September 26, 2019, 7:50am UTC](https://discourse.julialang.org/t/float64-comparison-operator-performance/29179/8 "2019-09-26T07:50:42Z")

</div>

I’d like to know best practices on whether packages should rely on `Ordering` internally and in external APIs.

Read through that linked issue, and it seems that ordering is here to stay.

DataFrames.jl uses ordering.  
[https://github.com/JuliaData/DataFrames.jl/blob/118a30007be0db10c6a5b1c939cd29526cf61f63/src/abstractdataframe/sort.jl#L73-L78](https://github.com/JuliaData/DataFrames.jl/blob/118a30007be0db10c6a5b1c939cd29526cf61f63/src/abstractdataframe/sort.jl#L73-L78)

DataStructures.jl has an open request to convert to ordering.  
[https://github.com/JuliaCollections/DataStructures.jl/issues/243](https://github.com/JuliaCollections/DataStructures.jl/issues/243)

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [September 26, 2019, 8:39am UTC](https://discourse.julialang.org/t/float64-comparison-operator-performance/29179/9 "2019-09-26T08:39:38Z")

</div>

My understanding (from reading various comments) is that

1. `Base.Ordering` [was necessary](https://github.com/JuliaCollections/DataStructures.jl/issues/243#issuecomment-273419834) as a compiler optimization (besides other useful purposes it served), but this is no longer true

2. but whether to remove it altogether and what (if anything) should replace it is not decided, because of other priorities.

So I would not rely on it for now.

Also, since it is neither exported nor mentioned in the manual, I don’t think it is part of the official API. Technically it could be removed at any time (but of course deprecating it first would be more elegant).
