# Tuple reverse is not type-stable enough

``````# julia 1.4
using AxisKeys
using Test

# "more" type-stable version of reverse
reverse_tuple(x) = ntuple(i -> x[length(x) + 1 - i], length(x))

f1(nt) = NamedDimsArray(zeros(1,1,1), keys(nt) |> reverse)
f2(nt) = NamedDimsArray(zeros(1,1,1), keys(nt) |> reverse_tuple)

@inferred f1((a=1,b=1,c=1))  # fails: keys not inferred
@inferred f2((a=1,b=1,c=1))  # passes
``````

Basically, function `f1` does not infer correctly, but `f2` does - and the only thing that differs is the tuple reversal function.
Note that `Base.reverse` is type-stable if used by itself, and the example above reproduces only when `reverse` is put inside something else.

Any idea why this happens and what’s going on?

You’re putting the `(:c, :b, :a)` tuple into a type parameter, so you’re not really fighting type-stability… you’re fighting constant propagation. Not only does Julia need to get the type of `reverse` correct, it needs to deduce (and mark) the fact that it’s going to return that particular value ahead of time. That in turn is affecting the downstream type-stability once you put the (hopefully) constant value into a type parameter.

1 Like

Note for sufficiently long tuples, `reverse` will become a gigantic runtime performance problem:

``````julia> @generated function myreverse(t::NTuple{N, Any}) where {N}
Expr(:tuple, (:(t[\$i]) for i in N:-1:1)...)
end
myreverse (generic function with 1 method)

julia> for n ∈ 1:5:50
tt = Ref(Tuple(1:n))
println("n = \$n")
print("  ");
@btime reverse(\$tt[])
print("  ");
@btime myreverse(\$tt[])
println(" ")
end
n = 1
1.289 ns (0 allocations: 0 bytes)
1.290 ns (0 allocations: 0 bytes)

n = 6
2.839 ns (0 allocations: 0 bytes)
2.909 ns (0 allocations: 0 bytes)

n = 11
3.439 ns (0 allocations: 0 bytes)
3.349 ns (0 allocations: 0 bytes)

n = 16
4.369 ns (0 allocations: 0 bytes)
4.489 ns (0 allocations: 0 bytes)

n = 21
4.229 ns (0 allocations: 0 bytes)
4.229 ns (0 allocations: 0 bytes)

n = 26
7.116 ns (0 allocations: 0 bytes)
6.939 ns (0 allocations: 0 bytes)

n = 31
5.819 ns (0 allocations: 0 bytes)
5.669 ns (0 allocations: 0 bytes)

n = 36
25.239 μs (72 allocations: 6.47 KiB)
9.749 ns (0 allocations: 0 bytes)

n = 41
31.559 μs (82 allocations: 8.19 KiB)
7.657 ns (0 allocations: 0 bytes)

n = 46
38.580 μs (92 allocations: 10.16 KiB)
12.381 ns (0 allocations: 0 bytes)
``````

but this is a separate issue from type stability.

Indeed, you are right - I didn’t notice that. It means `AxisKeys` interface is not type-stable, and nothing is suboptimal about `Base.reverse` implementation, right?
Then my `reverse_tuple` is somehow easier for the julia constant propagation, but it has nothing to do with type stability?

Some `reverse(::Tuple)` performance discussion references:

1 Like