Type instability in nested quadgk calls

mbasquens
Fri, 07 Mar 2025 08:59:54 +0000
Performance

Hi! I am having trouble with the type stability of quadgk when the integrand function is using quadgk itself. Here is the MWE:

using QuadGK

f(x) = quadgk(y -> x*y, 0., 1.)[1]
quadgk(f, 0., 1.)

Using code_warntype on the latter function gives that the return type is Tuple{Any, Any}:

julia> @code_warntype quadgk(f, 0., 1.)
MethodInstance for QuadGK.quadgk(::typeof(f), ::Float64, ::Float64)
  from quadgk(f, segs::T...; atol, rtol, maxevals, order, norm, segbuf, eval_segbuf) where T @ QuadGK ~/.julia/packages/QuadGK/7rND3/src/api.jl:80
Static Parameters
  T = Float64
Arguments
  #self#::Core.Const(QuadGK.quadgk)
  f::Core.Const(f)
  segs::Tuple{Float64, Float64}
Locals
  atol::Nothing
  rtol::Nothing
  maxevals::Int64
  order::Int64
  norm::typeof(LinearAlgebra.norm)
  segbuf::Nothing
  eval_segbuf::Nothing
Body::Tuple{Any, Any}
1 ─ %1  = QuadGK.nothing::Core.Const(nothing)
│         (atol = %1)
│   %3  = QuadGK.nothing::Core.Const(nothing)
│         (rtol = %3)
│   %5  = QuadGK.:^::Core.Const(^)
│   %6  = Core.apply_type(Base.Val, 7)::Core.Const(Val{7})
│   %7  = (%6)()::Core.Const(Val{7}())
│   %8  = Base.literal_pow(%5, 10, %7)::Core.Const(10000000)
│         (maxevals = %8)
│   %10 = 7::Core.Const(7)
│         (order = %10)
│   %12 = QuadGK.norm::Core.Const(LinearAlgebra.norm)
│         (norm = %12)
│   %14 = QuadGK.nothing::Core.Const(nothing)
│         (segbuf = %14)
│   %16 = QuadGK.nothing::Core.Const(nothing)
│         (eval_segbuf = %16)
│   %18 = QuadGK.:(var"#quadgk#49")::Core.Const(QuadGK.var"#quadgk#49")
│   %19 = Core.tuple(atol, rtol, maxevals::Core.Const(10000000), order::Core.Const(7), norm, segbuf, eval_segbuf, #self#, f)::Core.Const((nothing, nothing, 10000000, 7, LinearAlgebra.norm, nothing, nothing, QuadGK.quadgk, f))
│   %20 = Core._apply_iterate(Base.iterate, %18, %19, segs)::Tuple{Any, Any}
└──       return %20

I initially thought that maybe this was due to performance of captured variables in closures · Issue #15276 · JuliaLang/julia · GitHub, but the usual fix does not seem to help:

function g(x)
    G = let x=x
        y -> x*y
    end
    quadgk(G, 0., 1.)[1]
end
quadgk(g, 0., 1.)

The code_warntype here shows the same type unstable result Tuple{Any, Any}:

julia> @code_warntype quadgk(g, 0., 1.)
MethodInstance for QuadGK.quadgk(::typeof(g), ::Float64, ::Float64)
  from quadgk(f, segs::T...; atol, rtol, maxevals, order, norm, segbuf, eval_segbuf) where T @ QuadGK ~/.julia/packages/QuadGK/7rND3/src/api.jl:80
Static Parameters
  T = Float64
Arguments
  #self#::Core.Const(QuadGK.quadgk)
  f::Core.Const(g)
  segs::Tuple{Float64, Float64}
Locals
  atol::Nothing
  rtol::Nothing
  maxevals::Int64
  order::Int64
  norm::typeof(LinearAlgebra.norm)
  segbuf::Nothing
  eval_segbuf::Nothing
Body::Tuple{Any, Any}
1 ─ %1  = QuadGK.nothing::Core.Const(nothing)
│         (atol = %1)
│   %3  = QuadGK.nothing::Core.Const(nothing)
│         (rtol = %3)
│   %5  = QuadGK.:^::Core.Const(^)
│   %6  = Core.apply_type(Base.Val, 7)::Core.Const(Val{7})
│   %7  = (%6)()::Core.Const(Val{7}())
│   %8  = Base.literal_pow(%5, 10, %7)::Core.Const(10000000)
│         (maxevals = %8)
│   %10 = 7::Core.Const(7)
│         (order = %10)
│   %12 = QuadGK.norm::Core.Const(LinearAlgebra.norm)
│         (norm = %12)
│   %14 = QuadGK.nothing::Core.Const(nothing)
│         (segbuf = %14)
│   %16 = QuadGK.nothing::Core.Const(nothing)
│         (eval_segbuf = %16)
│   %18 = QuadGK.:(var"#quadgk#49")::Core.Const(QuadGK.var"#quadgk#49")
│   %19 = Core.tuple(atol, rtol, maxevals::Core.Const(10000000), order::Core.Const(7), norm, segbuf, eval_segbuf, #self#, f)::Core.Const((nothing, nothing, 10000000, 7, LinearAlgebra.norm, nothing, nothing, QuadGK.quadgk, g))
│   %20 = Core._apply_iterate(Base.iterate, %18, %19, segs)::Tuple{Any, Any}
└──       return %20

Both f and g seem to be type-stable. Is there something I am missing here?

27 posts - 8 participants