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

Assume size is non-negative for increased efficiency #50530

Merged
merged 3 commits into from
Jul 15, 2023

Conversation

LilithHafner
Copy link
Member

I noticed here that lastindex(x::Base.OneTo) is not simply x.stop. This PR performs that optimization and many more by assuming size always returns positive numbers.

julia> @code_native lastindex(Base.OneTo(5)) # master
        .section        __TEXT,__text,regular,pure_instructions
        .build_version macos, 13, 0
        .globl  _julia_lastindex_81             ; -- Begin function julia_lastindex_81
        .p2align        2
_julia_lastindex_81:                    ; @julia_lastindex_81
; ┌ @ abstractarray.jl:423 within `lastindex`
; %bb.0:                                ; %top
; │┌ @ abstractarray.jl:386 within `eachindex`
; ││┌ @ abstractarray.jl:134 within `axes1`
; │││┌ @ range.jl:708 within `axes`
; ││││┌ @ range.jl:471 within `oneto`
; │││││┌ @ range.jl:469 within `OneTo` @ range.jl:454
; ││││││┌ @ promotion.jl:532 within `max`
; │││││││┌ @ int.jl:83 within `<`
        ldr     x8, [x0]
; │││││││└
; │││││││┌ @ essentials.jl:642 within `ifelse`
        cmp     x8, #0
        csel    x0, x8, xzr, gt
; │└└└└└└└
        ret
; └
                                        ; -- End function
.subsections_via_symbols

julia> @code_native lastindex(Base.OneTo(5)) # pr
        .section        __TEXT,__text,regular,pure_instructions
        .build_version macos, 13, 0
        .globl  _julia_lastindex_13253          ; -- Begin function julia_lastindex_13253
        .p2align        2
_julia_lastindex_13253:                 ; @julia_lastindex_13253
; ┌ @ abstractarray.jl:423 within `lastindex`
; %bb.0:                                ; %top
        ldr     x0, [x0]
        ret
; └
                                        ; -- End function
.subsections_via_symbols

Also removed axes(r::AbstractRange) = (oneto(length(r)),) (added in #40382, @vtjnash) as redundant with the general axes method.

The obvious downside here is that if someone defines an object with negative size, its axes will include Base.OneTo with negative stop. I think that is acceptable, but if not, we can gate this optimization to a set of known types (all AbstractArray types defined in Base should have non-negative size)

@LilithHafner LilithHafner added performance Must go faster arrays [a, r, r, a, y, s] collections Data structures holding multiple items, e.g. sets labels Jul 12, 2023
@gbaraldi
Copy link
Member

Can't we add an assert on OneTo so that this is actually always true?

Copy link
Member

@vtjnash vtjnash left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM

@jishnub
Copy link
Contributor

jishnub commented Jul 13, 2023

Removing the dispatch on oneto will break InfiniteArrays, which specializes oneto:

julia> axes(1:∞, 1)
OneToInf()

julia> axes(1:∞, 1)[1]
1

whereas on this PR

julia> axes(1:∞, 1)
Base.OneTo(ℵ₀)

julia> axes(1:∞, 1)[1]
ERROR: MethodError: no method matching Infinities.InfiniteCardinal{0}(::Int64)

@LilithHafner
Copy link
Member Author

Can't we add an assert on OneTo so that this is actually always true?

I'm not sure what the point of @assert is. According to the docstring, Assert should only be used as a debugging tool. It also does have a performance penalty

julia> using BenchmarkTools
[ Info: Precompiling BenchmarkTools [6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf]

julia> function f(x)
           @assert x > 0
           x
       end
f (generic function with 1 method)

julia> @btime f(x) setup=(x=rand())
  3.083 ns (0 allocations: 0 bytes)
0.21786033077166012

julia> function g(x)
           x
       end
g (generic function with 1 method)

julia> @btime g(x) setup=(x=rand())
  1.500 ns (0 allocations: 0 bytes)
0.7776567415362038

Removing the dispatch on oneto will break InfiniteArrays, which specializes oneto:

Together, I don't think that breakage is sufficient to block this PR. @vtjnash, do you agree?

@gbaraldi
Copy link
Member

Asserts are a debugging tool/testing tool. In the C code they do nothing when disabled. We should probably disable the julia level assertions if the C ones aren't on, because they are there to enforce invariants in test mode.

@vtjnash
Copy link
Member

vtjnash commented Jul 13, 2023

Still LGTM

@jishnub
Copy link
Contributor

jishnub commented Jul 14, 2023

Base.OneTo(ℵ₀) smells better than Infinities.OneToInf()

julia> Base.OneTo(ℵ₀) |> typeof |> supertype
AbstractUnitRange{InfiniteCardinal{0}}

This appears to be a range with elements of type InfiniteCardinal{0}. What we really need is Base.OneTo(ℵ₀) to be a Base.OneTo{Int}.

@LilithHafner
Copy link
Member Author

Or Base.OneTo{T, S} <: AbstractUnitRange{T} to have eltype T and stop type S, not sure if that would be performance breaking, or have an impact on compile times, but it would help with this case a lot.

@LilithHafner LilithHafner added this pull request to the merge queue Jul 15, 2023
Merged via the queue into master with commit 191256e Jul 15, 2023
@LilithHafner LilithHafner deleted the assume-length-non-negative branch July 15, 2023 17:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
arrays [a, r, r, a, y, s] collections Data structures holding multiple items, e.g. sets performance Must go faster
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants