Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/jacobian.jl
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ jacobian(f, x::Real) = throw(DimensionMismatch("jacobian(f, x) expects that x is
#####################

function extract_jacobian!(::Type{T}, result::AbstractArray, ydual::AbstractArray, n) where {T}
out_reshaped = reshape(result, length(ydual), n)
out_reshaped = _maybe_reshape(result, length(ydual), n)
ydual_reshaped = vec(ydual)
# Use closure to avoid GPU broadcasting with Type
partials_wrap(ydual, nrange) = partials(T, ydual, nrange)
Expand All @@ -117,6 +117,16 @@ function extract_jacobian_chunk!(::Type{T}, result, ydual, index, chunksize) whe
return result
end

# Avoid allocating a ReshapedArray wrapper when `result` already has the target shape.
# reshape() always allocates a wrapper that cannot be elided under --check-bounds=yes.
@inline function _maybe_reshape(result::AbstractArray, m, n)
if size(result) == (m, n)
return result
else
return reshape(result, m, n)
end
Comment on lines +123 to +127
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not type-stable in general?

From searching through the codebase, my impression is that in quite a few cases we call extract_jacobian! internally with a matrix of the correct dimensions constructed in the previous line. In these cases, we don't even need this check and can always operate with result.

AFAICT the only case where we might have to reshape is when users provide an output array.

Maybe we could reshape user-provided arrays to matrices higher up in the call stack, maybe even in the user-facing function directly, and then never reshape in this internal function and only accept matrices.

The reshaping of the user-provided arrays could eg be done unconditionally for non-Matrix input (and other types for which we don't know whether the function in this draft would be type-stable) and only for Matrix (and other known to be type-stable types) we would use the conditional reshaping.

end

reshape_jacobian(result, ydual, xdual) = reshape(result, length(ydual), length(xdual))
reshape_jacobian(result::DiffResult, ydual, xdual) = reshape_jacobian(DiffResults.jacobian(result), ydual, xdual)

Expand Down
16 changes: 16 additions & 0 deletions test/AllocationsTest.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,20 @@ convert_test_574() = convert(ForwardDiff.Dual{Nothing,ForwardDiff.Dual{Nothing,F
@test iszero(allocs_convert_test_574())
end

@testset "Test jacobian! allocations" begin
# jacobian! should not allocate when called with a pre-allocated result Matrix.
# Previously, reshape() inside extract_jacobian! allocated a wrapper
# object that could not be elided under --check-bounds=yes.
function allocs_jacobian!()
f!(y, x) = (y .= x .^ 2)
x = [1.0, 2.0, 3.0]
y = similar(x)
result = zeros(3, 3)
cfg = ForwardDiff.JacobianConfig(f!, y, x)
ForwardDiff.jacobian!(result, f!, y, x, cfg) # warmup
return @allocated ForwardDiff.jacobian!(result, f!, y, x, cfg)
end
@test iszero(allocs_jacobian!())
end

end
Loading