Type instability with Union or heterogenous array/tuple

Hey Julianners,

I just cannot figure out how to make type stable code in case when we have a “sort of” heterogeneous array or tuple and we randomly pick an element.
I did this minimal example.

hn(qq) = begin
	local r
	q=qq[rand([1,3])]
	if isa(q, Int)
		r=4
	elseif isa(q,Float32)
		r=3f0
	else
		r=1e0
	end
	return r
end
@code_warntype hn((3,3f0,2))
hn((3,3f0,2))

Is this something that isn’t possible to resolve in any certain way in Julia?

Your function is fundamentally unstable, it literally draws a random type at runtime. Your type assertions only make individual branches type stable, but the return value of the function is still unknown to the compiler.

2 Likes

Yeah, you are right… I just want this to be type stable… :smiley:
I guess you basically proved it isn’t possible.

So:

fn(arr::heterogenous type) =
  x=arr[pick random] # only available in run time
  return x # cannot be deferred or fixed any certain way?
end

But then how to handle the heterogeneous types in Julia? :frowning:

In C++ we could write a very performant code with virtual functions I think. Does this something that cannot be accomplised with Julia?

You can use the trick with type assertions like you did, but inside the branches you should call a function that performs the work. You want to avoid the instability propagating to the compute heavy parts of your code.

1 Like

When you have heterogeneous data whose elements are accessed based on runtime information, try to minimize the code that runs in the uninferred state, i.e. wrap subsequent code in a function that can be specialized to the different types. Using the profiler, you can then check that often the run-time dispatch overhead is negligible. That is, the fact that the top function call is type-unstable is not actually detrimental to performance.

1 Like

So basically I could write something like this and it is optimal:

calc(a::Vector{Float32}) = a.+=1f0
calc(a::Vector{Float64}) = a.+=1e0
qn(hetarrs) = begin
  arr = hetarrs[rand([1,2])]
  calc(arr)
end

vv = (
randn(Float32,10000),
randn(Float64,10000),
)
# @code_warntype qn(vv)
qn(vv)

Yes, the advantage of doing it this way is that you pay the cost of the type instability a single time, at the call for calc.

1 Like

This approach is called a “function barrier” where qn is unstable, but the calc function serves as a barrier for the instability beyond which the types are known. The call to calc incurs dynamic dispatch, but calc itself is not affected.

2 Likes