From 24a30d642c5c4e1bf63d32c54236502fc0048e6d Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Mon, 20 Jul 2026 20:56:42 +0200 Subject: [PATCH 1/2] Inline `dual_definition_retval` to fix nested `Dual` performance `dual_definition_retval` carries no `@inline` annotation. For plain `Dual`s the inliner picks it up anyway, but for nested `Dual`s (as used by `hessian`) the struct is large enough that Julia's inlining cost model declines it. Every DiffRules-generated operation -- including `*` and `/` -- then compiles to a scalar op plus out-of-line calls that return the full `Dual` through an `sret` memcpy (648 bytes for `Dual{T,Dual{T,Float64,8},8}`, per call, per op). Forcing the inline turns e.g. the nested-`Dual` multiply into straight-line inlined code. Measured on `Dual{Nothing,Dual{Nothing,Float64,8},8}` vectors (256 elements, ns per element): | | before | after | |--------------|--------|-------| | `*` (M4) | 65.4 | 22.7 | | `*` (Zen 4) | 127.6 | 53.5 | | `sqrt` (M4) | 34.0 | 14.7 | | `sqrt` (Zen 4) | 81.1 | 13.0 | End-to-end `ForwardDiff.hessian!` at n=256, chunk 8: 28.3 -> 20.4 ms (ackley) and 41.2 -> 23.9 ms (rosenbrock) on an Apple M4; 46.3 -> 25.7 ms and 56.7 -> 34.0 ms on an AMD EPYC 9354. First-order (non-nested) `Dual`s already inlined before this change and are unaffected. Co-Authored-By: Claude Fable 5 --- src/dual.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dual.jl b/src/dual.jl index 7552dd5f..919536d5 100644 --- a/src/dual.jl +++ b/src/dual.jl @@ -210,10 +210,10 @@ macro define_ternary_dual_op(f, xyz_body, xy_body, xz_body, yz_body, x_body, y_b end # Support complex-valued functions such as `hankelh1` -function dual_definition_retval(::Val{T}, val::Real, deriv::Real, partial::Partials) where {T} +@inline function dual_definition_retval(::Val{T}, val::Real, deriv::Real, partial::Partials) where {T} return Dual{T}(val, deriv * partial) end -function dual_definition_retval(::Val{T}, val::Real, deriv1::Real, partial1::Partials, deriv2::Real, partial2::Partials) where {T} +@inline function dual_definition_retval(::Val{T}, val::Real, deriv1::Real, partial1::Partials, deriv2::Real, partial2::Partials) where {T} return Dual{T}(val, _mul_partials(partial1, partial2, deriv1, deriv2)) end function dual_definition_retval(::Val{T}, val::Complex, deriv::Union{Real,Complex}, partial::Partials) where {T} From 99022ce47b4bf68696e0c7529fa84781bf667071 Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Tue, 28 Jul 2026 12:16:02 +0200 Subject: [PATCH 2/2] Add `@inline` to the complex `dual_definition_retval` methods too Co-Authored-By: Claude Fable 5 --- src/dual.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dual.jl b/src/dual.jl index 919536d5..32eb2312 100644 --- a/src/dual.jl +++ b/src/dual.jl @@ -216,7 +216,7 @@ end @inline function dual_definition_retval(::Val{T}, val::Real, deriv1::Real, partial1::Partials, deriv2::Real, partial2::Partials) where {T} return Dual{T}(val, _mul_partials(partial1, partial2, deriv1, deriv2)) end -function dual_definition_retval(::Val{T}, val::Complex, deriv::Union{Real,Complex}, partial::Partials) where {T} +@inline function dual_definition_retval(::Val{T}, val::Complex, deriv::Union{Real,Complex}, partial::Partials) where {T} reval, imval = reim(val) if deriv isa Real p = deriv * partial @@ -226,7 +226,7 @@ function dual_definition_retval(::Val{T}, val::Complex, deriv::Union{Real,Comple return Complex(Dual{T}(reval, rederiv * partial), Dual{T}(imval, imderiv * partial)) end end -function dual_definition_retval(::Val{T}, val::Complex, deriv1::Union{Real,Complex}, partial1::Partials, deriv2::Union{Real,Complex}, partial2::Partials) where {T} +@inline function dual_definition_retval(::Val{T}, val::Complex, deriv1::Union{Real,Complex}, partial1::Partials, deriv2::Union{Real,Complex}, partial2::Partials) where {T} reval, imval = reim(val) if deriv1 isa Real && deriv2 isa Real p = _mul_partials(partial1, partial2, deriv1, deriv2)