mirror of
https://github.com/JuliaLang/julia.git
synced 2026-05-28 03:10:33 +08:00
I pointed Claude at [perf.julialang.org](https://perf.julialang.org/) and asked it to find opportunities to improve unusually slow benchmarks. See [JuliaCI/julia-ci-timing@main/analysis](https://github.com/JuliaCI/julia-ci-timing/tree/main/analysis) for some of the scripts it used. This is what it came up with: ---- Two small, BaseBenchmarks-driven perf fixes that share a theme — when the storage of an array already has the layout you want, dispatch to the bulk path on a `reinterpret` view instead of going through the per-element fallback. ### `NTuple{N,E}(::Union{Array,Memory})` for `N >= 32` The default `_totuple(::Type{All32{E,N}}, itr)` path collects into an intermediate `Vector` and splats. The `All32` cap exists so we don't specialize tuple construction on every `N`; this PR keeps that property (no per-`N` codegen) by adding a single fast path for `Array`/`Memory` inputs whose `isbits` element type matches the requested tuple element type. In that case the storage is layout-identical to the tuple, so a single `reinterpret` view + scalar load returns the tuple in O(1) (plus a length check). The fast path lives in `reinterpretarray.jl` because `reinterpret` on arrays isn't yet defined when `tuple.jl` is loaded. Over-long inputs are silently truncated to match the iterator-based fallback. Non-matching eltypes / non-isbits eltypes fall through to the existing path unchanged. ``` master this PR NTuple{40,Float64}(::Vector{Float64}) 1.36 μs 7.3 ns NTuple{150,Float64}(::Vector{Float64}) 4.92 μs 17.0 ns ``` (0 allocations on the fast path, vs. `N+~3` allocations and `~33 N` B on master.) ### Bulk `rand!(::Array{Complex{T}})` for `T <: HWReal` `rand!(::AbstractRNG, ::Array{Complex{T}})` previously fell through to the generic `AbstractArray` path — two scalar `rand` calls per element. A packed `Array{Complex{T}}` has the same memory layout as a length-`2N` `Array{T}`, so reinterpreting and dispatching to the bulk `rand!` for `T` (SIMD-vectorized for `HWReal` on Xoshiro/TaskLocalRNG, and pointer- based for MersenneTwister) is correct and substantially faster. Per @vtjnash's feedback in earlier review, this avoids `unsafe_wrap`/`unsafe_load` entirely. Instead, the existing bulk dispatches in `MersenneTwister` and `XoshiroSimd` are widened to also accept a `NonReshapedReinterpretArray` over a `MutableDenseArrayType` parent. `pointer`/`unsafe_convert` are already defined for `ReinterpretArray` and forward to the parent's storage, so the existing `GC.@preserve A ... pointer(A) ...` machinery keeps working unchanged. Speedups on a length-1000 `Vector{Complex{T}}`, default RNG: ``` master this PR Complex{Float16} 3.5 μs 1.5 μs 2.4× Complex{Float32} 3.4 μs 2.2 μs 1.5× Complex{Float64} 3.1 μs 3.1 μs ≈ Complex{Int8} 3.1 μs 539 ns 5.7× Complex{Int16} 3.1 μs 1.1 μs 3.0× Complex{Int32} 3.1 μs 1.9 μs 1.7× Complex{Int64} 4.6 μs 4.2 μs 1.1× Complex{UInt8} 3.1 μs 532 ns 5.9× Complex{UInt16} 3.1 μs 1.0 μs 2.9× Complex{UInt32} 3.0 μs 1.9 μs 1.6× Complex{UInt64} 4.7 μs 4.2 μs 1.1× ``` All variants are 0-allocation. `Complex{Float64}` and the 64-bit integer variants were already close to the bulk path's throughput on master and see only a small win. A reproducibility test was added so equal MT seeds keep producing equal sequences for `Vector{Complex{T}}`. --- Disclosure: written with the assistance of generative AI (GitHub Copilot / Claude). --------- Co-authored-by: GitHub Copilot <noreply@github.com>
704 lines
21 KiB
Julia
704 lines
21 KiB
Julia
# This file is a part of Julia. License is MIT: https://julialang.org/license
|
||
|
||
import Core: Tuple
|
||
|
||
# Document NTuple here where we have everything needed for the doc system
|
||
"""
|
||
NTuple{N, T}
|
||
|
||
A compact way of representing the type for a tuple of length `N` where all elements are of type `T`.
|
||
|
||
# Examples
|
||
```jldoctest
|
||
julia> isa((1, 2, 3, 4, 5, 6), NTuple{6, Int})
|
||
true
|
||
```
|
||
|
||
See also [`ntuple`](@ref).
|
||
"""
|
||
NTuple
|
||
|
||
# convenience function for extracting N from a Tuple (if defined)
|
||
# else return `nothing` for anything else given (such as Vararg or other non-sized Union)
|
||
_counttuple(::Type{<:NTuple{N,Any}}) where {N} = N
|
||
_counttuple(::Type) = nothing
|
||
|
||
## indexing ##
|
||
|
||
length(@nospecialize t::Tuple) = nfields(t)
|
||
firstindex(@nospecialize t::Tuple) = 1
|
||
lastindex(@nospecialize t::Tuple) = length(t)
|
||
size(@nospecialize(t::Tuple), d::Integer) = (d == 1) ? length(t) : throw(ArgumentError("invalid tuple dimension $d"))
|
||
axes(@nospecialize t::Tuple) = (OneTo(length(t)),)
|
||
getindex(@nospecialize(t::Tuple), i::Int) = getfield(t, i, @_boundscheck)
|
||
getindex(@nospecialize(t::Tuple), i::Integer) = getfield(t, convert(Int, i), @_boundscheck)
|
||
__safe_getindex(@nospecialize(t::Tuple), i::Int) = (@_nothrow_noub_meta; getfield(t, i, false))
|
||
getindex(t::Tuple, r::AbstractArray{<:Any,1}) = (eltype(t)[t[ri] for ri in r]...,)
|
||
getindex(t::Tuple, b::AbstractArray{Bool,1}) = length(b) == length(t) ? getindex(t, findall(b)) : throw(BoundsError(t, b))
|
||
getindex(t::Tuple, c::Colon) = t
|
||
|
||
get(t::Tuple, i::Integer, default) = i in 1:length(t) ? getindex(t, i) : default
|
||
get(f::Callable, t::Tuple, i::Integer) = i in 1:length(t) ? getindex(t, i) : f()
|
||
|
||
# returns new tuple; N.B.: becomes no-op if `i` is out-of-bounds
|
||
|
||
"""
|
||
setindex(t::Tuple, v, i::Integer)
|
||
|
||
Create a new tuple similar to `t` with the value at index `i` set to `v`.
|
||
Throws a `BoundsError` when out of bounds.
|
||
|
||
# Examples
|
||
```jldoctest
|
||
julia> Base.setindex((1, 2, 6), 2, 3) == (1, 2, 2)
|
||
true
|
||
```
|
||
"""
|
||
function setindex(x::Tuple, v, i::Integer)
|
||
@boundscheck 1 <= i <= length(x) || throw(BoundsError(x, i))
|
||
@inline
|
||
_setindex(v, i, x...)
|
||
end
|
||
|
||
function _setindex(v, i::Integer, args::Vararg{Any,N}) where {N}
|
||
@inline
|
||
return ntuple(j -> ifelse(j == i, v, args[j]), Val{N}())::NTuple{N, Any}
|
||
end
|
||
|
||
|
||
## iterating ##
|
||
|
||
function iterate(@nospecialize(t::Tuple), i::Int=1)
|
||
@inline
|
||
@_nothrow_meta
|
||
return (1 <= i <= length(t)) ? (t[i], i + 1) : nothing
|
||
end
|
||
|
||
keys(@nospecialize t::Tuple) = OneTo(length(t))
|
||
|
||
"""
|
||
prevind(A, i)
|
||
|
||
Return the index before `i` in `A`. The returned index is often equivalent to
|
||
`i - 1` for an integer `i`. This function can be useful for generic code.
|
||
|
||
!!! warning
|
||
The returned index might be out of bounds. Consider using
|
||
[`checkbounds`](@ref).
|
||
|
||
See also: [`nextind`](@ref).
|
||
|
||
# Examples
|
||
```jldoctest
|
||
julia> x = [1 2; 3 4]
|
||
2×2 Matrix{Int64}:
|
||
1 2
|
||
3 4
|
||
|
||
julia> prevind(x, 4) # valid result
|
||
3
|
||
|
||
julia> prevind(x, 1) # invalid result
|
||
0
|
||
|
||
julia> prevind(x, CartesianIndex(2, 2)) # valid result
|
||
CartesianIndex(1, 2)
|
||
|
||
julia> prevind(x, CartesianIndex(1, 1)) # invalid result
|
||
CartesianIndex(2, 0)
|
||
```
|
||
"""
|
||
function prevind end
|
||
|
||
"""
|
||
nextind(A, i)
|
||
|
||
Return the index after `i` in `A`. The returned index is often equivalent to
|
||
`i + 1` for an integer `i`. This function can be useful for generic code.
|
||
|
||
!!! warning
|
||
The returned index might be out of bounds. Consider using
|
||
[`checkbounds`](@ref).
|
||
|
||
See also: [`prevind`](@ref).
|
||
|
||
# Examples
|
||
```jldoctest
|
||
julia> x = [1 2; 3 4]
|
||
2×2 Matrix{Int64}:
|
||
1 2
|
||
3 4
|
||
|
||
julia> nextind(x, 1) # valid result
|
||
2
|
||
|
||
julia> nextind(x, 4) # invalid result
|
||
5
|
||
|
||
julia> nextind(x, CartesianIndex(1, 1)) # valid result
|
||
CartesianIndex(2, 1)
|
||
|
||
julia> nextind(x, CartesianIndex(2, 2)) # invalid result
|
||
CartesianIndex(1, 3)
|
||
```
|
||
"""
|
||
function nextind end
|
||
|
||
prevind(@nospecialize(t::Tuple), i::Integer) = Int(i)-1
|
||
nextind(@nospecialize(t::Tuple), i::Integer) = Int(i)+1
|
||
|
||
function keys(t::Tuple, t2::Tuple...)
|
||
@inline
|
||
lent = length(t)
|
||
if !all(==(lent) ∘ length, t2)
|
||
let inds = map(only ∘ axes, (t, t2...))
|
||
throw_eachindex_mismatch_indices("indices", inds...)
|
||
end
|
||
end
|
||
Base.OneTo(lent)
|
||
end
|
||
|
||
# this allows partial evaluation of bounded sequences of next() calls on tuples,
|
||
# while reducing to plain next() for arbitrary iterables.
|
||
indexed_iterate(t::Tuple, i::Int, state=1) = (@inline; (getfield(t, i), i+1))
|
||
indexed_iterate(a::Union{Array,Memory}, i::Int, state=1) = (@inline; (a[i], i+1))
|
||
function indexed_iterate(I, i)
|
||
x = iterate(I)
|
||
x === nothing && throw(BoundsError(I, i))
|
||
x
|
||
end
|
||
function indexed_iterate(I, i, state)
|
||
x = iterate(I, state)
|
||
x === nothing && throw(BoundsError(I, i))
|
||
x
|
||
end
|
||
|
||
"""
|
||
Base.rest(collection[, itr_state])
|
||
|
||
Generic function for taking the tail of `collection`, starting from a specific iteration
|
||
state `itr_state`. Return a `Tuple`, if `collection` itself is a `Tuple`, a subtype of
|
||
`AbstractVector`, if `collection` is an `AbstractArray`, a subtype of `AbstractString`
|
||
if `collection` is an `AbstractString`, and an arbitrary iterator, falling back to
|
||
`Iterators.rest(collection[, itr_state])`, otherwise.
|
||
|
||
Can be overloaded for user-defined collection types to customize the behavior of [slurping
|
||
in assignments](@ref destructuring-assignment) in final position, like `a, b... = collection`.
|
||
|
||
!!! compat "Julia 1.6"
|
||
`Base.rest` requires at least Julia 1.6.
|
||
|
||
See also: [`first`](@ref first), [`Iterators.rest`](@ref), [`Base.split_rest`](@ref).
|
||
|
||
# Examples
|
||
```jldoctest
|
||
julia> a = [1 2; 3 4]
|
||
2×2 Matrix{Int64}:
|
||
1 2
|
||
3 4
|
||
|
||
julia> first, state = iterate(a)
|
||
(1, 2)
|
||
|
||
julia> first, Base.rest(a, state)
|
||
(1, [3, 2, 4])
|
||
```
|
||
"""
|
||
function rest end
|
||
rest(t::Tuple) = t
|
||
function rest(t::Tuple, i)
|
||
let i = i::Int
|
||
ntuple(x -> getfield(t, x+i-1), length(t)-i+1)
|
||
end
|
||
end
|
||
rest(a::Union{Array,Memory,Core.SimpleVector}, i=1) = a[(i::Int):end]
|
||
rest(itr, state...) = Iterators.rest(itr, state...)
|
||
|
||
"""
|
||
Base.split_rest(collection, n::Int[, itr_state]) -> (rest_but_n, last_n)
|
||
|
||
Generic function for splitting the tail of `collection`, starting from a specific iteration
|
||
state `itr_state`. Returns a tuple of two new collections. The first one contains all
|
||
elements of the tail but the `n` last ones, which make up the second collection.
|
||
|
||
The type of the first collection generally follows that of [`Base.rest`](@ref), except that
|
||
the fallback case is not lazy, but is collected eagerly into a vector.
|
||
|
||
Can be overloaded for user-defined collection types to customize the behavior of [slurping
|
||
in assignments](@ref destructuring-assignment) in non-final position, like `a, b..., c = collection`.
|
||
|
||
!!! compat "Julia 1.9"
|
||
`Base.split_rest` requires at least Julia 1.9.
|
||
|
||
See also: [`Base.rest`](@ref).
|
||
|
||
# Examples
|
||
```jldoctest
|
||
julia> a = [1 2; 3 4]
|
||
2×2 Matrix{Int64}:
|
||
1 2
|
||
3 4
|
||
|
||
julia> first, state = iterate(a)
|
||
(1, 2)
|
||
|
||
julia> first, Base.split_rest(a, 1, state)
|
||
(1, ([3, 2], [4]))
|
||
```
|
||
"""
|
||
function split_rest end
|
||
function split_rest(itr, n::Int, state...)
|
||
if IteratorSize(itr) == IsInfinite()
|
||
throw(ArgumentError("Cannot split an infinite iterator in the middle."))
|
||
end
|
||
return _split_rest(rest(itr, state...), n)
|
||
end
|
||
_split_rest(itr, n::Int) = _split_rest(collect(itr), n)
|
||
function _check_length_split_rest(len, n)
|
||
len < n && throw(ArgumentError(
|
||
"The iterator only contains $len elements, but at least $n were requested."
|
||
))
|
||
end
|
||
function _split_rest(a::Union{AbstractArray, Core.SimpleVector}, n::Int)
|
||
_check_length_split_rest(length(a), n)
|
||
return a[begin:end-n], a[end-n+1:end]
|
||
end
|
||
|
||
@eval _split_tuple(t::Tuple, n::Int, i::Int=1) = ($(Expr(:meta, :aggressive_constprop)); (t[i:n], t[n+1:end]))
|
||
|
||
@eval split_rest(t::Tuple, n::Int, i=1) = ($(Expr(:meta, :aggressive_constprop)); _split_tuple(t, length(t)-n, Int(i)))
|
||
|
||
# Use dispatch to avoid a branch in first
|
||
first(::Tuple{}) = throw(ArgumentError("tuple must be non-empty"))
|
||
first(t::Tuple) = t[1]
|
||
|
||
# eltype
|
||
|
||
# the <: here makes the runtime a bit more complicated (needing to check isdefined), but really helps inference
|
||
_eltype_ntuple(t::Type{<:Tuple{Vararg{E}}}) where {E} = @isdefined(E) ? (E isa Type ? E : Union{}) : _compute_eltype(t)
|
||
const _eltype_ntuple_sig_condition = (Type{<:Tuple{Vararg{E}}} where E)
|
||
# We'd like to be able to infer eltype(::Tuple), so keep the number of eltype(::Type{<:Tuple}) methods at max_methods!
|
||
function eltype(t::Type{<:Tuple})
|
||
if t <: Tuple{}
|
||
Bottom
|
||
elseif isa(t, _eltype_ntuple_sig_condition)
|
||
_eltype_ntuple(t)
|
||
else
|
||
_compute_eltype(t)
|
||
end
|
||
end
|
||
function _compute_eltype(@nospecialize t)
|
||
@_total_meta
|
||
has_free_typevars(t) && return Any
|
||
t´ = unwrap_unionall(t)
|
||
# Given t = Tuple{Vararg{S}} where S<:Real, the various
|
||
# unwrapping/wrapping/va-handling here will return Real
|
||
if t´ isa Union
|
||
return promote_typejoin(_compute_eltype(rewrap_unionall(t´.a, t)),
|
||
_compute_eltype(rewrap_unionall(t´.b, t)))
|
||
end
|
||
p = (t´::DataType).parameters
|
||
length(p) == 0 && return Union{}
|
||
elt = rewrap_unionall(unwrapva(p[1]), t)
|
||
elt isa Type || return Union{} # Tuple{2} is legal as a Type, but the eltype is Union{} since it is uninhabited
|
||
r = elt
|
||
for i in 2:length(p)
|
||
r === Any && return r # if we've already reached Any, it can't widen any more
|
||
elt = rewrap_unionall(unwrapva(p[i]), t)
|
||
elt isa Type || return Union{} # Tuple{2} is legal as a Type, but the eltype is Union{} since it is uninhabited
|
||
r = promote_typejoin(elt, r)
|
||
end
|
||
return r
|
||
end
|
||
|
||
# key/val types
|
||
keytype(@nospecialize t::Tuple) = keytype(typeof(t))
|
||
keytype(@nospecialize T::Type{<:Tuple}) = Int
|
||
|
||
valtype(@nospecialize t::Tuple) = valtype(typeof(t))
|
||
valtype(@nospecialize T::Type{<:Tuple}) = eltype(T)
|
||
|
||
# version of tail that doesn't throw on empty tuples (used in array indexing)
|
||
safe_tail(t::Tuple) = tail(t)
|
||
safe_tail(t::Tuple{}) = ()
|
||
|
||
# front (the converse of tail: it skips the last entry)
|
||
|
||
"""
|
||
front(x::Tuple)::Tuple
|
||
|
||
Return a `Tuple` consisting of all but the last component of `x`.
|
||
|
||
See also: [`first`](@ref), [`tail`](@ref Base.tail).
|
||
|
||
# Examples
|
||
```jldoctest
|
||
julia> Base.front((1,2,3))
|
||
(1, 2)
|
||
|
||
julia> Base.front(())
|
||
ERROR: ArgumentError: Cannot call front on an empty tuple.
|
||
```
|
||
"""
|
||
function front(t::Tuple)
|
||
@inline
|
||
_front(t...)
|
||
end
|
||
_front() = throw(ArgumentError("Cannot call front on an empty tuple."))
|
||
_front(v) = ()
|
||
function _front(v, t...)
|
||
@inline
|
||
(v, _front(t...)...)
|
||
end
|
||
|
||
## mapping ##
|
||
|
||
# 1 argument function
|
||
map(f, t::Tuple{}) = ()
|
||
map(f, t::Tuple{Any,}) = (@inline; (f(t[1]),))
|
||
map(f, t::Tuple{Any, Any}) = (@inline; (f(t[1]), f(t[2])))
|
||
map(f, t::Tuple{Any, Any, Any}) = (@inline; (f(t[1]), f(t[2]), f(t[3])))
|
||
map(f, t::Tuple) = (@inline; (f(t[1]), map(f,tail(t))...))
|
||
# stop inlining after some number of arguments to avoid code blowup
|
||
const Any32{N} = Tuple{Any,Any,Any,Any,Any,Any,Any,Any,
|
||
Any,Any,Any,Any,Any,Any,Any,Any,
|
||
Any,Any,Any,Any,Any,Any,Any,Any,
|
||
Any,Any,Any,Any,Any,Any,Any,Any,
|
||
Vararg{Any,N}}
|
||
const All32{T,N} = Tuple{T,T,T,T,T,T,T,T,
|
||
T,T,T,T,T,T,T,T,
|
||
T,T,T,T,T,T,T,T,
|
||
T,T,T,T,T,T,T,T,
|
||
Vararg{T,N}}
|
||
function map(f, t::Any32)
|
||
n = length(t)
|
||
A = Vector{Any}(undef, n)
|
||
for i=1:n
|
||
A[i] = f(t[i])
|
||
end
|
||
(A...,)
|
||
end
|
||
# 2 argument function
|
||
map(f, t::Tuple{}, s::Tuple{}) = ()
|
||
map(f, t::Tuple, s::Tuple{}) = ()
|
||
map(f, t::Tuple{}, s::Tuple) = ()
|
||
map(f, t::Tuple{Any,}, s::Tuple{Any,}) = (@inline; (f(t[1],s[1]),))
|
||
map(f, t::Tuple{Any,Any}, s::Tuple{Any,Any}) = (@inline; (f(t[1],s[1]), f(t[2],s[2])))
|
||
function map(f, t::Tuple, s::Tuple)
|
||
@inline
|
||
(f(t[1],s[1]), map(f, tail(t), tail(s))...)
|
||
end
|
||
function map(f, t::Any32, s::Any32)
|
||
n = min(length(t), length(s))
|
||
A = Vector{Any}(undef, n)
|
||
for i = 1:n
|
||
A[i] = f(t[i], s[i])
|
||
end
|
||
(A...,)
|
||
end
|
||
# n argument function
|
||
heads(ts::Tuple...) = map(t -> t[1], ts)
|
||
tails(ts::Tuple...) = map(tail, ts)
|
||
map(f, ::Tuple{}, ::Tuple{}...) = ()
|
||
anyempty(x::Tuple{}, xs...) = true
|
||
anyempty(x::Tuple, xs...) = anyempty(xs...)
|
||
anyempty() = false
|
||
function map(f, t1::Tuple, t2::Tuple, ts::Tuple...)
|
||
@inline
|
||
anyempty(t1, t2, ts...) && return ()
|
||
(f(heads(t1, t2, ts...)...), map(f, tails(t1, t2, ts...)...)...)
|
||
end
|
||
function map(f, t1::Any32, t2::Any32, ts::Any32...)
|
||
n = min(length(t1), length(t2), minimum(length, ts))
|
||
A = Vector{Any}(undef, n)
|
||
for i = 1:n
|
||
A[i] = f(t1[i], t2[i], map(t -> t[i], ts)...)
|
||
end
|
||
(A...,)
|
||
end
|
||
|
||
# type-stable padding
|
||
fill_to_length(t::NTuple{N,Any}, val, ::Val{N}) where {N} = t
|
||
fill_to_length(t::Tuple{}, val, ::Val{1}) = (val,)
|
||
fill_to_length(t::Tuple{Any}, val, ::Val{2}) = (t..., val)
|
||
fill_to_length(t::Tuple{}, val, ::Val{2}) = (val, val)
|
||
#function fill_to_length(t::Tuple, val, ::Val{N}) where {N}
|
||
# @inline
|
||
# return (t..., ntuple(i -> val, N - length(t))...)
|
||
#end
|
||
|
||
# constructing from an iterator
|
||
|
||
function tuple_type_tail(T::Type)
|
||
@_foldable_meta # TODO: this method is wrong (and not :foldable)
|
||
if isa(T, UnionAll)
|
||
return UnionAll(T.var, tuple_type_tail(T.body))
|
||
elseif isa(T, Union)
|
||
return Union{tuple_type_tail(T.a), tuple_type_tail(T.b)}
|
||
else
|
||
T.name === Tuple.name || throw(MethodError(tuple_type_tail, (T,)))
|
||
if isvatuple(T) && length(T.parameters) == 1
|
||
va = unwrap_unionall(T.parameters[1])::Core.TypeofVararg
|
||
(isdefined(va, :N) && isa(va.N, Int)) || return T
|
||
return Tuple{Vararg{va.T, va.N-1}}
|
||
end
|
||
return Tuple{argtail(T.parameters...)...}
|
||
end
|
||
end
|
||
|
||
(::Type{T})(x::Tuple) where {T<:Tuple} = x isa T ? x : convert(T, x) # still use `convert` for tuples
|
||
|
||
Tuple(x::Ref) = tuple(getindex(x)) # faster than iterator for one element
|
||
Tuple(x::Array{T,0}) where {T} = tuple(getindex(x))
|
||
|
||
(::Type{T})(itr) where {T<:Tuple} = _totuple(T, itr)
|
||
|
||
_totuple(::Type{Tuple{}}, itr, s...) = ()
|
||
|
||
function _totuple_err(@nospecialize T)
|
||
@noinline
|
||
throw(ArgumentError(LazyString("too few elements for tuple type ", T)))
|
||
end
|
||
|
||
function _totuple(::Type{T}, itr, s::Vararg{Any,N}) where {T,N}
|
||
@inline
|
||
y = iterate(itr, s...)
|
||
y === nothing && _totuple_err(T)
|
||
T1 = fieldtype(T, 1)
|
||
y1 = y[1]
|
||
t1 = y1 isa T1 ? y1 : convert(T1, y1)::T1
|
||
# inference may give up in recursive calls, so annotate here to force accurate return type to be propagated
|
||
rT = tuple_type_tail(T)
|
||
ts = _totuple(rT, itr, y[2])::rT
|
||
return (t1, ts...)::T
|
||
end
|
||
|
||
# use iterative algorithm for long tuples
|
||
function _totuple(T::Type{All32{E,N}}, itr) where {E,N}
|
||
len = N+32
|
||
elts = collect(E, Iterators.take(itr,len))
|
||
if length(elts) != len
|
||
_totuple_err(T)
|
||
end
|
||
(elts...,)
|
||
end
|
||
|
||
# fast path for Array/Memory lives in reinterpretarray.jl
|
||
|
||
_totuple(::Type{Tuple{Vararg{E}}}, itr, s...) where {E} = (collect(E, Iterators.rest(itr,s...))...,)
|
||
|
||
_totuple(::Type{Tuple}, itr, s...) = (collect(Iterators.rest(itr,s...))...,)
|
||
|
||
# for types that `apply` knows about, just splatting is faster than collecting first
|
||
_totuple(::Type{Tuple}, itr::Array) = (itr...,)
|
||
_totuple(::Type{Tuple}, itr::SimpleVector) = (itr...,)
|
||
_totuple(::Type{Tuple}, itr::NamedTuple) = (itr...,)
|
||
_totuple(::Type{Tuple}, p::Pair) = (p.first, p.second)
|
||
_totuple(::Type{Tuple}, x::Number) = (x,) # to make Tuple(x) inferable
|
||
|
||
## find ##
|
||
|
||
_findfirst_rec(f, i::Int, ::Tuple{}) = nothing
|
||
_findfirst_rec(f, i::Int, t::Tuple) = (@inline; f(first(t)) ? i : _findfirst_rec(f, i+1, tail(t)))
|
||
function _findfirst_loop(f::Function, t)
|
||
for i in eachindex(t)
|
||
f(t[i]) && return i
|
||
end
|
||
return nothing
|
||
end
|
||
findfirst(f::Function, t::Tuple) = length(t) < 32 ? _findfirst_rec(f, 1, t) : _findfirst_loop(f, t)
|
||
|
||
findlast(f::Function, t::Tuple) = length(t) < 32 ? _findlast_rec(f, t) : _findlast_loop(f, t)
|
||
function _findlast_rec(f::Function, x::Tuple)
|
||
r = findfirst(f, reverse(x))
|
||
return isnothing(r) ? r : length(x) - r + 1
|
||
end
|
||
function _findlast_loop(f::Function, t)
|
||
for i in reverse(1:length(t))
|
||
f(t[i]) && return i
|
||
end
|
||
return nothing
|
||
end
|
||
|
||
## filter ##
|
||
|
||
filter_rec(f, xs::Tuple) = afoldl((ys, x) -> f(x) ? (ys..., x) : ys, (), xs...)
|
||
|
||
# use Array for long tuples
|
||
filter(f, t::Tuple) = length(t) < 32 ? filter_rec(f, t) : Tuple(filter(f, collect(t)))
|
||
|
||
## comparison ##
|
||
|
||
isequal(t1::Tuple, t2::Tuple) = length(t1) == length(t2) && _isequal(t1, t2)
|
||
_isequal(::Tuple{}, ::Tuple{}) = true
|
||
function _isequal(t1::Tuple{Any,Vararg{Any}}, t2::Tuple{Any,Vararg{Any}})
|
||
return isequal(t1[1], t2[1]) && _isequal(tail(t1), tail(t2))
|
||
end
|
||
function _isequal(t1::Any32, t2::Any32)
|
||
for i in eachindex(t1, t2)
|
||
if !isequal(t1[i], t2[i])
|
||
return false
|
||
end
|
||
end
|
||
return true
|
||
end
|
||
|
||
==(t1::Tuple, t2::Tuple) = (length(t1) == length(t2)) && _eq(t1, t2)
|
||
_eq(t1::Tuple{}, t2::Tuple{}) = true
|
||
_eq_missing(t1::Tuple{}, t2::Tuple{}) = missing
|
||
function _eq(t1::Tuple, t2::Tuple)
|
||
eq = t1[1] == t2[1]
|
||
if eq === false
|
||
return false
|
||
elseif ismissing(eq)
|
||
return _eq_missing(tail(t1), tail(t2))
|
||
else
|
||
return _eq(tail(t1), tail(t2))
|
||
end
|
||
end
|
||
function _eq_missing(t1::Tuple, t2::Tuple)
|
||
eq = t1[1] == t2[1]
|
||
if eq === false
|
||
return false
|
||
else
|
||
return _eq_missing(tail(t1), tail(t2))
|
||
end
|
||
end
|
||
function _eq(t1::Any32, t2::Any32)
|
||
anymissing = false
|
||
for i in eachindex(t1, t2)
|
||
eq = (t1[i] == t2[i])
|
||
if ismissing(eq)
|
||
anymissing = true
|
||
elseif !eq
|
||
return false
|
||
end
|
||
end
|
||
return anymissing ? missing : true
|
||
end
|
||
|
||
const tuplehash_seed = UInt === UInt64 ? 0x77cfa1eef01bca90 : 0xf01bca90
|
||
hash(::Tuple{}, h::UInt) = h ⊻ tuplehash_seed
|
||
hash(t::Tuple, h::UInt) = hash(t[1], hash(tail(t), h))
|
||
function hash(t::Any32, h::UInt)
|
||
out = h ⊻ tuplehash_seed
|
||
for i = length(t):-1:1
|
||
out = hash(t[i], out)
|
||
end
|
||
return out
|
||
end
|
||
|
||
<(::Tuple{}, ::Tuple{}) = false
|
||
<(::Tuple{}, ::Tuple) = true
|
||
<(::Tuple, ::Tuple{}) = false
|
||
function <(t1::Tuple, t2::Tuple)
|
||
a, b = t1[1], t2[1]
|
||
eq = (a == b)
|
||
if ismissing(eq)
|
||
return missing
|
||
elseif !eq
|
||
return a < b
|
||
end
|
||
return tail(t1) < tail(t2)
|
||
end
|
||
function <(t1::Any32, t2::Any32)
|
||
n1, n2 = length(t1), length(t2)
|
||
for i = 1:min(n1, n2)
|
||
a, b = t1[i], t2[i]
|
||
eq = (a == b)
|
||
if ismissing(eq)
|
||
return missing
|
||
elseif !eq
|
||
return a < b
|
||
end
|
||
end
|
||
return n1 < n2
|
||
end
|
||
|
||
isless(::Tuple{}, ::Tuple{}) = false
|
||
isless(::Tuple{}, ::Tuple) = true
|
||
isless(::Tuple, ::Tuple{}) = false
|
||
|
||
"""
|
||
isless(t1::Tuple, t2::Tuple)
|
||
|
||
Return `true` when `t1` is less than `t2` in lexicographic order.
|
||
"""
|
||
function isless(t1::Tuple, t2::Tuple)
|
||
a, b = t1[1], t2[1]
|
||
isless(a, b) || (isequal(a, b) && isless(tail(t1), tail(t2)))
|
||
end
|
||
function isless(t1::Any32, t2::Any32)
|
||
n1, n2 = length(t1), length(t2)
|
||
for i = 1:min(n1, n2)
|
||
a, b = t1[i], t2[i]
|
||
if !isequal(a, b)
|
||
return isless(a, b)
|
||
end
|
||
end
|
||
return n1 < n2
|
||
end
|
||
|
||
## functions ##
|
||
|
||
isempty(x::Tuple{}) = true
|
||
isempty(@nospecialize x::Tuple) = false
|
||
|
||
revargs() = ()
|
||
revargs(x, r...) = (revargs(r...)..., x)
|
||
|
||
reverse(t::Tuple) = revargs(t...)
|
||
|
||
"""
|
||
isassigned(v::Tuple, i::Integer) -> Bool
|
||
|
||
Return `true` if index `i` is within the bounds of tuple `v`, i.e. `1 ≤ i ≤ length(v)`.
|
||
|
||
!!! compat "Julia 1.13"
|
||
This method requires at least Julia 1.13.
|
||
"""
|
||
function isassigned(v::Tuple, i::Integer)
|
||
@boundscheck 1 <= i <= length(v) || return false
|
||
true
|
||
end
|
||
|
||
## specialized reduction ##
|
||
|
||
prod(x::Tuple{}) = 1
|
||
# This is consistent with the regular prod because there is no need for size promotion
|
||
# if all elements in the tuple are of system size.
|
||
# It is defined here separately in order to support bootstrap, because it's needed earlier
|
||
# than the general prod definition is available.
|
||
prod(x::Tuple{Int, Vararg{Int}}) = *(x...)
|
||
|
||
# a version of `in` esp. for NamedTuple, to make it pure, and not compiled for each tuple length
|
||
function sym_in(x::Symbol, itr::Tuple{Vararg{Symbol}})
|
||
@noinline
|
||
@_total_meta
|
||
for y in itr
|
||
y === x && return true
|
||
end
|
||
return false
|
||
end
|
||
in(x::Symbol, itr::Tuple{Vararg{Symbol}}) = sym_in(x, itr)
|
||
|
||
|
||
"""
|
||
empty(x::Tuple)
|
||
|
||
Return an empty tuple, `()`.
|
||
"""
|
||
empty(@nospecialize x::Tuple) = ()
|
||
|
||
foreach(f, itr::Tuple) = foldl((_, x) -> (f(x); nothing), itr, init=nothing)
|
||
foreach(f, itr::Tuple, itrs::Tuple...) = foldl((_, xs) -> (f(xs...); nothing), zip(itr, itrs...), init=nothing)
|
||
|
||
circshift((@nospecialize t::Union{Tuple{},Tuple{Any}}), @nospecialize _::Integer) = t
|
||
circshift(t::Tuple{Any,Any}, shift::Integer) = iseven(shift) ? t : reverse(t)
|
||
function circshift(x::Tuple{Any,Any,Any,Vararg{Any,N}}, shift::Integer) where {N}
|
||
@inline
|
||
len = N + 3
|
||
j = mod1(shift, len)
|
||
ntuple(k -> getindex(x, k-j+ifelse(k>j,0,len)), Val(len))::Tuple
|
||
end
|