Skip to content

Commit

Permalink
Speed up first and only for various types (#52296)
Browse files Browse the repository at this point in the history
  • Loading branch information
matthias314 authored Dec 4, 2023
1 parent f761860 commit bb7091c
Show file tree
Hide file tree
Showing 6 changed files with 24 additions and 3 deletions.
2 changes: 1 addition & 1 deletion base/abstractarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ julia> firstindex(rand(3,4,5), 2)
firstindex(a::AbstractArray) = (@inline; first(eachindex(IndexLinear(), a)))
firstindex(a, d) = (@inline; first(axes(a, d)))

first(a::AbstractArray) = a[first(eachindex(a))]
@propagate_inbounds first(a::AbstractArray) = a[first(eachindex(a))]

"""
first(coll)
Expand Down
4 changes: 4 additions & 0 deletions base/dict.jl
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,8 @@ end
isempty(t::Dict) = (t.count == 0)
length(t::Dict) = t.count

@propagate_inbounds Iterators.only(t::Dict) = Iterators._only(t, first)

@propagate_inbounds function Base.iterate(v::T, i::Int = v.dict.idxfloor) where T <: Union{KeySet{<:Any, <:Dict}, ValueIterator{<:Dict}}
i == 0 && return nothing
i = skip_deleted(v.dict, i)
Expand Down Expand Up @@ -1049,3 +1051,5 @@ iterate(dict::PersistentDict, state=nothing) = HAMT.iterate(dict.trie, state)
length(dict::PersistentDict) = HAMT.length(dict.trie)
isempty(dict::PersistentDict) = HAMT.isempty(dict.trie)
empty(::PersistentDict, ::Type{K}, ::Type{V}) where {K, V} = PersistentDict{K, V}()

@propagate_inbounds Iterators.only(dict::PersistentDict) = Iterators._only(dict, first)
15 changes: 13 additions & 2 deletions base/iterators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const Base = parentmodule(@__MODULE__)
using .Base:
@inline, Pair, Pairs, AbstractDict, IndexLinear, IndexStyle, AbstractVector, Vector,
SizeUnknown, HasLength, HasShape, IsInfinite, EltypeUnknown, HasEltype, OneTo,
@propagate_inbounds, @isdefined, @boundscheck, @inbounds, Generator,
@propagate_inbounds, @isdefined, @boundscheck, @inbounds, Generator, IdDict,
AbstractRange, AbstractUnitRange, UnitRange, LinearIndices, TupleOrBottom,
(:), |, +, -, *, !==, !, ==, !=, <=, <, >, >=, missing,
any, _counttuple, eachindex, ntuple, zero, prod, reduce, in, firstindex, lastindex,
Expand Down Expand Up @@ -1547,7 +1547,9 @@ Stacktrace:
[...]
```
"""
@propagate_inbounds function only(x)
@propagate_inbounds only(x) = _only(x, iterate)

@propagate_inbounds function _only(x, ::typeof(iterate))
i = iterate(x)
@boundscheck if i === nothing
throw(ArgumentError("Collection is empty, must contain exactly 1 element"))
Expand All @@ -1559,6 +1561,15 @@ Stacktrace:
return ret
end

@inline function _only(x, ::typeof(first))
@boundscheck if length(x) != 1
throw(ArgumentError("Collection must contain exactly 1 element"))
end
@inbounds first(x)
end

@propagate_inbounds only(x::IdDict) = _only(x, first)

# Specific error messages for tuples and named tuples
only(x::Tuple{Any}) = x[1]
only(x::Tuple) = throw(
Expand Down
2 changes: 2 additions & 0 deletions base/set.jl
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ rehash!(s::Set) = (rehash!(s.dict); s)

iterate(s::Set, i...) = iterate(KeySet(s.dict), i...)

@propagate_inbounds Iterators.only(s::Set) = Iterators._only(s, first)

# In case the size(s) is smaller than size(t) its more efficient to iterate through
# elements of s instead and only delete the ones also contained in t.
# The threshold for this decision boils down to a tradeoff between
Expand Down
2 changes: 2 additions & 0 deletions base/strings/basic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ firstindex(s::AbstractString) = 1
lastindex(s::AbstractString) = thisind(s, ncodeunits(s)::Int)
isempty(s::AbstractString) = iszero(ncodeunits(s)::Int)

@propagate_inbounds first(s::AbstractString) = s[firstindex(s)]

function getindex(s::AbstractString, i::Integer)
@boundscheck checkbounds(s, i)
@inbounds return isvalid(s, i) ? (iterate(s, i)::NTuple{2,Any})[1] : string_index_err(s, i)
Expand Down
2 changes: 2 additions & 0 deletions base/weakkeydict.jl
Original file line number Diff line number Diff line change
Expand Up @@ -213,4 +213,6 @@ function iterate(t::WeakKeyDict{K,V}, state...) where {K, V}
end
end

@propagate_inbounds Iterators.only(d::WeakKeyDict) = Iterators._only(d, first)

filter!(f, d::WeakKeyDict) = filter_in_one_pass!(f, d)

0 comments on commit bb7091c

Please sign in to comment.