Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ForwardDiff 10x slower than FiniteDiff equivalent #502

Closed
Deduction42 opened this issue Jan 26, 2021 · 2 comments
Closed

ForwardDiff 10x slower than FiniteDiff equivalent #502

Deduction42 opened this issue Jan 26, 2021 · 2 comments

Comments

@Deduction42
Copy link

I'm trying to do a flash point calculation and I will have to take derivatives of this. The problem is that the flash point calculation requires solving a nonlinear equation iteratively. This results in ForwardDiff being 10x as slow as FiniteDiff. I know performance can vary, but I thought AD methods were supposed to outperform non-AD methods.

using Roots
using ForwardDiff
using FiniteDiff

function evaporated_frac(n, K)
    RetType = promote_type( eltype(n), eltype(K) )
    (kMin, kMax) = extrema(K)

    if kMin > 1 #Everything is a gas
        return RetType(1.0)
    elseif kMax < 1 #Everything is a liquid
        return RetType(0.0)
    end

    #Acquire mole fractions
    z  = n./sum(n)
    dK = K .- 1.0

    #Set up Rachford-Rice equation
    function err(β) 
        return sum( (z.*dK) ./ (1 .+ β.*dK) )
    end

    #Set up initial guess and limits for Brent's method
    ϵ = 1e-6
    βLim = sort( 1 ./ (1 .- [kMax, kMin]) )

    β = find_zero(err, βLim+[ϵ,-ϵ], Roots.Brent())
    return clamp(β, 0.0, 1.0)
end

n = 50 .* rand(10)
K = 2 .* rand(10)

@time β  = evaporated_frac(n, K)
@time g1 = FiniteDiff.finite_difference_gradient(x->evaporated_frac(n,x), K)
@time g2 = ForwardDiff.gradient(x->evaporated_frac(n,x), K)

0.000019 seconds (26 allocations: 2.953 KiB)
0.031502 seconds (62.05 k allocations: 3.497 MiB)
0.941194 seconds (5.06 M allocations: 248.243 MiB, 5.24% gc time)

@mewilhel
Copy link

mewilhel commented Mar 8, 2021

Generally, it is. For timing code in Julia, it's best to use the BenchmarkTools.jl package and @btime rather than using the @time macro. The former will characterize the run-time of the function once it's JIT compiled whereas the later will time a number of other processes as well (in this case primarily the garbage collector).

using BenchmarkTools

@btime evaporated_frac($n, $K)
@btime FiniteDiff.finite_difference_gradient(x->evaporated_frac(n,x), $K)
@btime ForwardDiff.gradient(x->evaporated_frac(n,x), $K)

3.112 μs (28 allocations: 3.41 KiB)
63.899 μs (575 allocations: 68.81 KiB)
9.999 μs (45 allocations: 20.80 KiB)
@time evaporated_frac(n, K)
@time FiniteDiff.finite_difference_gradient(x->evaporated_frac(n,x), K)
@time ForwardDiff.gradient(x->evaporated_frac(n,x), K)

0.000019 seconds (32 allocations: 3.641 KiB)
0.061883 seconds (402.62 k allocations: 21.280 MiB, 6.28% gc time)
0.682719 seconds (5.02 M allocations: 246.400 MiB, 5.78% gc time)

@KristofferC
Copy link
Collaborator

Nice demonstration @mewilhel. I think we can close this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants