-
Notifications
You must be signed in to change notification settings - Fork 9
Description
I tracked down a per-row allocation in the FSM join TPC-H Q1 to a call to unsafe_load!(::Date) that was not type stable. The issue was that unsafe_load! is a @generated function that calls itself recursively, and Julia does not appear to make any guarantees about type inference in this scenario.
Here is an example that demonstrates the issue:
@generated function foo(::Val{N}) where N
if N < 1
:(1)
else
# Return 1+foo(Val(N-1)), but using Int(N) for even
# and Float64(N) for odd
if N % 2 == 0
:(1+foo(Val(Int(N)-1)))
else
:(1+foo(Val(Float64(N)-1)))
end
end
end
If you start a new julia 1.0.1 session, paste in the above definition, and then try these calls, you’ll get type stable code:
julia> @code_typed foo(Val(2))
CodeInfo(
2 1 ─ (Base.sub_float)(1.0, 1.0)::Float64
└── return 3
) => Int64
julia> @code_typed foo(Val(3))
CodeInfo(
2 1 ─ (Base.sub_float)(3.0, 1.0)::Float64
│ (Base.sub_float)(1.0, 1.0)::Float64
└── return 4
) => Int64
julia> @code_typed foo(Val(4))
CodeInfo(
2 1 ─ (Base.sub_float)(3.0, 1.0)::Float64
│ (Base.sub_float)(1.0, 1.0)::Float64
└── return 5
) => Int64
However, if you restart julia, paste in the definition of foo(), and immediately try foo(Val(4)), it will produce unstable code:
julia> @code_typed foo(Val(4))
CodeInfo(
2 1 ─ %1 = invoke Main.foo($(QuoteNode(Val{3}()))::Val{3})::Any
│ %2 = (1 + %1)::Any
└── return %2
) => Any
It’s the same call as before, but without the previous calls to foo(Val(3)) and foo(Val(2)) in the cache, the result of type inference is different.
In the case of unsafe_load!(::Blob{Date}), there was a recursion from unsafe_load!(::Blob{Date}) to unsafe_load!(::Blob{UTInstant{Day}}) to unsafe_load!(::Blob{Day}) to unsafe_load!(::Int64), and type inference was failing, resulting in an ::Any type:
