I am in the process of porting over some Matlab code that implements the Vector Field Navigation approached outline in Goncalves et al. 2010 leveraging AD. The code is working properly, but I am confused why two seemingly equivalent implementations results in significantly different timings and # of allocations.
VF_grad()
, takes function gradients as its arguments, while VF_func()
takes the original function and then determines the gradients.
using ForwardDiff
using StaticArrays
using LinearAlgebra
using BenchmarkTools
α1(x::AbstractArray) = (x[1]/13.0).^2.0 .+ (x[2]/7.0).^2.0 .- 1.0
α2(x::AbstractArray) = x[3]
V(x::AbstractArray) = -sqrt(α1(x)^2.0 + α2(x)^2.0);
∇α1(x::AbstractArray) = ForwardDiff.gradient(α1,x)
∇α2(x::AbstractArray) = ForwardDiff.gradient(α2,x)
∇V(x::AbstractArray) = ForwardDiff.gradient(V,x);
function VF_grad(x::AbstractArray, ∇α1::Function, ∇α2::Function, ∇V::Function, G::Real=1.0, H::Real=1.0)
∇α1x = ∇α1(x)
∇α2x = ∇α2(x)
circ = ∇α1x × ∇α2x
converge = ∇V(x)
G*converge + H*circ
end;
function VF_func(x::AbstractArray, α1::Function, α2::Function, V::Function, G::Real=1.0, H::Real=1.0)
∇α1x = ForwardDiff.gradient(α1,x)
∇α2x = ForwardDiff.gradient(α2,x)
circ = ∇α1x × ∇α2x
converge = ForwardDiff.gradient(V,x)
G*converge + H*circ
end;
## Make Data
eval_array = [SVector(rand(3)...) for i ∈ 1:750];
@btime VF_func.($eval_array, α1, α2, V)
@btime VF_grad.($eval_array, ∇α1, ∇α2, ∇V);
259.922 μs (9004 allocations: 275.58 KiB)
53.775 μs (4 allocations: 17.77 KiB)
Can somebody explain this behavior to me? Both look to be type-stable.
For some reason this behavior seems connected to ×
. Changing circ = ∇α1x × ∇α2x
to circ = ∇α1x
leads to
51.266 μs (4 allocations: 17.77 KiB)
51.253 μs (4 allocations: 17.77 KiB)
Also, if I use the original definition of circ
but modify my return to G*converge + circ
, i.e. no H*
I get
51.489 μs (4 allocations: 17.77 KiB)
51.497 μs (4 allocations: 17.77 KiB)