Type-instability indexing tuple

Is it possible to modify the following piece of code such that it becomes type-stable?

julia> function f(x)
       for k in eachindex(x)
       display(x[k])
       end
       end;

julia> a = tuple(1:2,1.0:2.0)
(1:2, 1.0:1.0:2.0)

julia> f(a)
1:2
1.0:1.0:2.0

julia> @code_warntype f(a)
Variables
  #self#::Core.Compiler.Const(f, false)
  x::Tuple{UnitRange{Int64},StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}}
  @_3::Union{Nothing, Tuple{Int64,Int64}}
  k::Int64

Body::Nothing
1 ─ %1  = Main.eachindex(x)::Core.Compiler.Const(Base.OneTo(2), false)
│         (@_3 = Base.iterate(%1))
│   %3  = (@_3::Core.Compiler.Const((1, 1), false) === nothing)::Core.Compiler.Const(false, false)
│   %4  = Base.not_int(%3)::Core.Compiler.Const(true, false)
└──       goto #4 if not %4
2 ┄ %6  = @_3::Tuple{Int64,Int64}::Tuple{Int64,Int64}
│         (k = Core.getfield(%6, 1))
│   %8  = Core.getfield(%6, 2)::Int64
│   %9  = Base.getindex(x, k)::Union{StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}, U
nitRange{Int64}}
│         Main.display(%9)
│         (@_3 = Base.iterate(%1, %8))
│   %12 = (@_3 === nothing)::Bool
│   %13 = Base.not_int(%12)::Bool
└──       goto #4 if not %13
3 ─       goto #2
4 ┄       return

Thank you very much in advance!

This code can not be made to be type stable. That said, it is unlikely to matter here.

3 Likes

display is itself not stable and you can’t really fix that (nor is it a function that you might care about trying to), but for small tuples, you can get rid of the type instability introduced by the loop by using map instead. E.g. compare the output of:

function f(x)
   for k in eachindex(x)
      identity(x[k])
   end
end
@code_warntype f(a)

which will show the getindex is not inferred (since the value of k is unknown at compile time and the return type will depend on it):

│   %9  = Base.getindex(x, k)::Union{StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}, UnitRange{Int64}}

vs. the output of:

function f(x)
   map(identity, x)
end
@code_warntype f(a)

which is fully inferred. Note though even this will not be inferred if the tuple has more than 16 elements.

1 Like

Thank you very much for your quick responses! :slight_smile: I should have chosen another MWE since I did not care about the display part, only about the indexing into the tuple. Unfortunately, it is not possible to use map for my purposes as I have to index into a product-iterator on the right-hand side…

However, I have fixed the type instability by promoting the types within the initial tuple, which, in spite of my concerns, did not significantly slow things down.

You can often use ntuple(i -> f(tup[i]), length(tup)) in place of a for loop, which like map behaves well for tuples.

3 Likes

My first approach to my problem had exactly this form and accordingly comprises only two lines of code. Unfortunately, it seems like there is no way to speed things up significantly… Filling the tuple manually with a for-loop enables the use of multiple threads, which helps only in case of large inputs.

Thanks a lot for your reply! :slight_smile: