The question is basically, which is “better” in terms of intermediate type-stability and/or performance:
function half_promote(x :: T) :: T where T <: AbstractFloat
half = 1//2
return half * x
end
or
function half_convert(x :: T) :: T where T <: AbstractFloat
half = oftype(x, 1//2)
return half * x
end
?
For type-stability I look at the @code_warntype
(for which the man-page is good but doesn’t seem to answer my question) below, but I can’t seem to extract a meaningful difference other than the explicit conversion in the latter.
julia> @code_warntype half_promote(2.0)
MethodInstance for half_promote(::Float64)
from half_promote(x::T) where T<:AbstractFloat @ Main REPL[6]:1
Static Parameters
T = Float64
Arguments
#self#::Core.Const(Main.half_promote)
x::Float64
Locals
half::Rational{Int64}
@_4::Float64
Body::Float64
1 ─ %1 = $(Expr(:static_parameter, 1))::Core.Const(Float64)
│ (half = 1 // 2)
│ %3 = half::Core.Const(1//2)
│ %4 = (%3 * x)::Float64
│ (@_4 = %4)
│ %6 = @_4::Float64
│ %7 = (%6 isa %1)::Core.Const(true)
└── goto #3 if not %7
2 ─ goto #4
3 ─ Core.Const(:(@_4))
│ Core.Const(:(Base.convert(%1, %10)))
└── Core.Const(:(@_4 = Core.typeassert(%11, %1)))
4 ┄ %13 = @_4::Float64
└── return %13
and
julia> @code_warntype half_convert(2.0)
MethodInstance for half_convert(::Float64)
from half_convert(x::T) where T<:AbstractFloat @ Main REPL[7]:1
Static Parameters
T = Float64
Arguments
#self#::Core.Const(Main.half_convert)
x::Float64
Locals
half::Float64
@_4::Float64
Body::Float64
1 ─ %1 = $(Expr(:static_parameter, 1))::Core.Const(Float64)
│ %2 = Main.oftype::Core.Const(oftype)
│ %3 = (1 // 2)::Core.Const(1//2)
│ (half = (%2)(x, %3))
│ %5 = half::Core.Const(0.5)
│ %6 = (%5 * x)::Float64
│ (@_4 = %6)
│ %8 = @_4::Float64
│ %9 = (%8 isa %1)::Core.Const(true)
└── goto #3 if not %9
2 ─ goto #4
3 ─ Core.Const(:(@_4))
│ Core.Const(:(Base.convert(%1, %12)))
└── Core.Const(:(@_4 = Core.typeassert(%13, %1)))
4 ┄ %15 = @_4::Float64
└── return %15
Thankfully, the LLVM lowering is a little more concise and provides nearly identical results with the only difference being in the comments.
julia> @code_llvm half_promote(2.0)
; Function Signature: half_promote(Float64)
; @ REPL[6]:1 within `half_promote`
define double @julia_half_promote_10502(double %"x::Float64") #0 {
top:
; @ REPL[6]:3 within `half_promote`
; ┌ @ promotion.jl:430 within `*` @ float.jl:493
%0 = fmul double %"x::Float64", 5.000000e-01
; └
ret double %0
}
and
julia> @code_llvm half_convert(2.0)
; Function Signature: half_convert(Float64)
; @ REPL[7]:1 within `half_convert`
define double @julia_half_convert_10519(double %"x::Float64") #0 {
top:
; @ REPL[7]:3 within `half_convert`
; ┌ @ float.jl:493 within `*`
%0 = fmul double %"x::Float64", 5.000000e-01
; └
ret double %0
}
So does the identical IR actually mean there is literally no difference between these functions?