I have recently already asked related questions, but type prediction for union types has unfortunately become a gift for me that keeps on giving.
Replacing Base.promote_typeof by a simpler implementation seems at no loss for me, but makes an v = [a,b,c,d] equivalent quite a lot faster when for instance a,b,c,d::Union{Float64,Int} is inferred. It also leads to perfect type prediction v::Union{Vector{Float64}, Vector{Int}} whereas Base implementation does not.
I was in particular suprised that use of X,Y,Z in (x::X, y::Y, z::Z) where {X,Y,Z} deteriorates type prediction compared to typeof(x),typeof(y),typeof(z), although I am not sure if this is strictly related to the rest.
I thought at first this could be some intentional avoided specialization, but experiments do not seem to support this. Maybe you can help me out.
What is unfortunate, but a compromise?
Or has something gone unnoticed?
Or what did I just get wrong?
Quite a long code example, so the @code_warntype output is each directly after the function definition. Please note that the k[] construction is just for demonstrational purposes. Also Float64 and Int just mean to represent an actual implementation example.
The most relevant lines taken from the full code are:
bar2(::X, ::Y, ::Z) where {X,Y,Z} = k[]::promote_type(promote_type(X, Y), Z) # ::Any (!)
bar4_4(x, y, z) = k[]::Base.promote_typeof(x, y, z, z) # ::Any (!)
bar5_4(x, y, z) = k[]::nosp_promote_typeof(x, y, z, z) # ::Union{Float64,Int}
sp_promote_typeof(x, args::Vararg{Any,N}) where N = promote_type(typeof(x), sp_promote_typeof(args...))
nosp_promote_typeof(x, args...) = promote_type(typeof(x), nosp_promote_typeof(args...))
sp_v = sp_vect(a, b, c, d, e, f) # Union{Vector{Float64}, Vector{Int64}}
nosp_v = nosp_vect(a, b, c, d, e, f) # Union{Vector{Float64}, Vector{Int64}}
base_v = base_vect(a, b, c, d, e, f) # Vector
## (case for large difference in compilation time for the three versions)
# 0.000568 seconds (3.23 k allocations: 169.469 KiB, 42.16% compilation time)
# 0.000708 seconds (2.00 k allocations: 109.375 KiB)
# 0.005754 seconds (15.93 k allocations: 603.641 KiB, 80.30% compilation time)
## (case for large difference in runtime for the three versions)
# 0.000252 seconds (2.00 k allocations: 109.375 KiB)
# 0.000707 seconds (2.00 k allocations: 109.375 KiB)
# 0.000984 seconds (6.00 k allocations: 203.125 KiB)
Full code:
## promote_type(of)
k = Ref{Any}()
nosp_promote_typeof(x) = typeof(x)
nosp_promote_typeof(x, args...) = promote_type(typeof(x), nosp_promote_typeof(args...))
bar1(::X, ::Y, ::Z) where {X,Y,Z} = k[]::promote_type(X, Y, Z) # ::Any
bar2(::X, ::Y, ::Z) where {X,Y,Z} = k[]::promote_type(promote_type(X, Y), Z) # ::Any (!)
bar3(x, y, z) = k[]::promote_type(typeof(x), typeof(y), typeof(z)) # ::Union{Float64,Int}
bar4(x, y, z) = k[]::Base.promote_typeof(x, y, z) # ::Union{Float64,Int}
bar4_4(x, y, z) = k[]::Base.promote_typeof(x, y, z, z) # ::Any (!)
bar5(x, y, z) = k[]::nosp_promote_typeof(x, y, z) # ::Union{Float64,Int}
bar5_4(x, y, z) = k[]::nosp_promote_typeof(x, y, z, z) # ::Union{Float64,Int}
function test3(f::F) where F<:Function
a = rand((1, 1.0))
b = rand((1, 1.0))
c = rand((1, 1.0))
result = f(a, b, c)
end
for f = (bar1, bar2, bar3, bar4, bar4_4, bar5, bar5_4)
println(f)
@code_warntype test3(f)
end
## union behaves simiarly
k = Ref{Any}()
union(::Type{X}, ::Type{Y}) where {X,Y} = Union{X,Y}
union(::Type{X}, ::Type{Y}, ::Type{Z}) where {X,Y,Z} = union(union(X, Y), Z)
foo1(::X, ::Y, ::Z) where {X,Y,Z} = k[]::Union{X,Y,Z} # ::Any
foo2(::X, ::Y, ::Z) where {X,Y,Z} = k[]::union(union(X, Y), Z) # ::Any
foo3(::X, ::Y, ::Z) where {X,Y,Z} = k[]::union(X, Y, Z) # ::Any
foo4(x, y, z) = k[]::Union{typeof(x),typeof(y),typeof(z)} # ::Any
foo5(x, y, z) = k[]::union(typeof(x), typeof(y), typeof(z)) # ::Union{Float64, Int64}
for f = (foo1, foo2, foo3, foo4, foo5)
println(f)
@code_warntype test3(f)
end
## vectors
sp_promote_typeof(x) = typeof(x)
sp_promote_typeof(x, args::Vararg{Any,N}) where N = promote_type(typeof(x), sp_promote_typeof(args...))
function sp_vect(x...)
T = sp_promote_typeof(x...)
v = Vector{T}(undef, length(x))
for i = eachindex(x)
v[i] = x[i]
end
return v
end
function nosp_vect(x...)
T = nosp_promote_typeof(x...)
v = Vector{T}(undef, length(x))
for i = eachindex(x)
v[i] = x[i]
end
return v
end
function base_vect(x...)
T = Base.promote_typeof(x...)
v = Vector{T}(undef, length(x))
for i = eachindex(x)
v[i] = x[i]
end
return v
end
function test3()
a = rand((1, 1.0))
b = rand((1, 1.0))
c = rand((1, 1.0))
sp_v = sp_vect(a, b, c) # Union{Vector{Float64}, Vector{Int64}}
nosp_v = nosp_vect(a, b, c) # Union{Vector{Float64}, Vector{Int64}}
base_v = base_vect(a, b, c) # Union{Vector{Float64}, Vector{Int64}}
end
@code_warntype test3()
function test6()
a = rand((1, 1.0))
b = rand((1, 1.0))
c = rand((1, 1.0))
d = rand((1, 1.0))
e = rand((1, 1.0))
f = rand((1, 1.0))
sp_v = sp_vect(a, b, c, d, e, f) # Union{Vector{Float64}, Vector{Int64}}
nosp_v = nosp_vect(a, b, c, d, e, f) # Union{Vector{Float64}, Vector{Int64}}
base_v = base_vect(a, b, c, d, e, f) # Vector
end
@code_warntype test6()
function time6()
a = rand((1, 1.0))
b = rand((1, 1.0))
c = rand((1, 1.0))
d = rand((1, 1.0))
e = rand((1, 1.0))
f = rand((1, 1.0))
@time for i = 1:1000
sp_vect(a, b, c, d, e, f)
end
@time for i = 1:1000
nosp_vect(a, b, c, d, e, f)
end
@time for i = 1:1000
base_vect(a, b, c, d, e, f)
end
return nothing
end
for i = 1:4
println("---")
time6()
end
# ---
# 0.019506 seconds (22.79 k allocations: 1.087 MiB, 98.36% compilation time)
# 0.019999 seconds (14.85 k allocations: 727.273 KiB, 96.17% compilation time)
# 0.018908 seconds (70.11 k allocations: 3.387 MiB, 94.52% compilation time)
# ---
# 0.008233 seconds (11.40 k allocations: 564.055 KiB, 96.47% compilation time)
# 0.008273 seconds (6.96 k allocations: 352.367 KiB, 92.64% compilation time)
# 0.017095 seconds (21.27 k allocations: 927.219 KiB, 93.83% compilation time)
# ---
# 0.000508 seconds (3.24 k allocations: 169.859 KiB, 46.22% compilation time)
# 0.000699 seconds (2.00 k allocations: 109.375 KiB)
# 0.009744 seconds (14.13 k allocations: 647.312 KiB, 89.49% compilation time)
# ---
# 0.000494 seconds (3.24 k allocations: 169.453 KiB, 46.85% compilation time)
# 0.004030 seconds (3.25 k allocations: 168.812 KiB, 86.05% compilation time)
# 0.011168 seconds (63.04 k allocations: 2.987 MiB, 90.28% compilation time)
## after some reruns
# ---
# 0.000470 seconds (2.30 k allocations: 123.906 KiB, 37.92% compilation time)
# 0.000556 seconds (2.00 k allocations: 109.375 KiB)
# 0.001050 seconds (8.00 k allocations: 234.375 KiB)
# ---
# 0.000316 seconds (2.30 k allocations: 123.844 KiB, 14.97% compilation time)
# 0.000533 seconds (2.00 k allocations: 109.375 KiB)
# 0.001026 seconds (10.00 k allocations: 265.625 KiB)
# ---
# 0.000263 seconds (2.00 k allocations: 109.375 KiB)
# 0.000638 seconds (2.00 k allocations: 109.375 KiB)
# 0.001049 seconds (6.00 k allocations: 203.125 KiB)
# ---
# 0.000252 seconds (2.00 k allocations: 109.375 KiB)
# 0.000707 seconds (2.00 k allocations: 109.375 KiB)
# 0.000984 seconds (6.00 k allocations: 203.125 KiB)
## another first time run
# ---
# 0.020731 seconds (19.28 k allocations: 943.086 KiB, 98.19% compilation time)
# 0.020742 seconds (17.21 k allocations: 842.578 KiB, 95.89% compilation time)
# 0.020964 seconds (73.27 k allocations: 3.470 MiB, 94.22% compilation time)
# ---
# 0.009201 seconds (11.40 k allocations: 564.258 KiB, 96.52% compilation time)
# 0.013132 seconds (8.20 k allocations: 411.664 KiB, 95.84% compilation time)
# 0.020222 seconds (68.17 k allocations: 3.229 MiB, 94.24% compilation time)
# ---
# 0.000467 seconds (2.93 k allocations: 154.438 KiB, 36.04% compilation time)
# 0.000622 seconds (2.00 k allocations: 109.375 KiB)
# 0.007555 seconds (56.36 k allocations: 2.620 MiB, 85.31% compilation time)
# ---
# 0.000568 seconds (3.23 k allocations: 169.469 KiB, 42.16% compilation time)
# 0.000708 seconds (2.00 k allocations: 109.375 KiB)
# 0.005754 seconds (15.93 k allocations: 603.641 KiB, 80.30% compilation time)