Type stability: ProfileView and @code_warntype don't agree

I have some code that I feel should run faster (see below). So I ran it with @profile and looked at the results with ProfileView.view() and as expected a lot of red showed up (i.e. runtime method lookup), the correspinding lines are commented in the code below. But when I run @code_warntype on the same function I get no red and it seems, all the code is nice and type stable. As far as I see ΔE is type stable and returns exactly what the annotation says, so why does it still get flagged as needing runtime method lookup?
And how do correctly annotate / write the code, such that no runtime method lookup is needed.

up = CartesianIndex(0, 1)
right  = CartesianIndex(1, 0)
down = CartesianIndex(0, -1)
left = CartesianIndex(-1, 0)

"Calculate the total energy of 2D Ising model with coupling J and field H"
function energy(lattice, J, H)::Float64
    energy = 0.0
    for i in eachindex(lattice)
        energy -= H*lattice[i]*(1. + J * lattice[i+up] + J * lattice[i+right])
    end
    return energy
end


"Calculate the total magnetization of the lattice"
magnetization(lattice)::Int = sum(lattice)


"The energy difference when flipping the spin on `site`"
@inline @propagate_inbounds function ΔE(lattice, site, J, H)::Float64
    out = (  2H*lattice[site]   # runtime method lookup here
     + 2J*lattice[site]*(lattice[site+up] + lattice[site+down]
                       + lattice[site+left] + lattice[site+right]))
    out
end


"Make a Markov Chain Monte Carlo Step on the lattice"
function MCMCStep!(lattice,
                   J,
                   H)::Tuple{Float64,Int}
    site = rand(CartesianIndices(lattice))
    @inbounds δE = ΔE(lattice, site, J, H)  # runtime method lookup here
    if δE < 0.0
        @inbounds δM = -lattice[site]
        @inbounds lattice[site] = δM
    elseif exp(-δE) > rand()
        @inbounds δM = -lattice[site]
        @inbounds lattice[site] = δM  # runtime method lookup
    else
        δE = 0.0::Float64
        δM = 0::Int
    end
    return (δE, 2*δM)   #this line gets flagged as slow
end


function MCMCRun(lattice, steps, J, H) where {N}
    E0 = energy(lattice, J, H)
    M0 = magnetization(lattice)
    E = E0
    M = M0
    energies = Array{Float64}(undef, steps)
    magnetizations = Array{Int64}(undef, steps)
    for i in 1:steps
        for j in 1:length(lattice)
            δE, δM = MCMCStep!(lattice, J, H)  # this is where all the time is spent with a runtime method lookup
            E += δE
            M += δM
        end
        energies[i] = E
        magnetizations[i] = M
    end
    E1 = energy(lattice, J, H)
    M1 = magnetization(lattice)
    return energies, magnetizations
end

You’re using non-constant globals, the first performance gotcha: https://docs.julialang.org/en/latest/manual/performance-tips/#Avoid-global-variables-1

Try

const up = CartesianIndex(0, 1)
const right  = CartesianIndex(1, 0)
const down = CartesianIndex(0, -1)
const left = CartesianIndex(-1, 0)
4 Likes

Oops, changed that now. But the main problem, that I spent most of my time in the last line of MCMCStep! (so at the return statement) persists…

It’s less obvious now. You’ll have to provide more than just the code, give enough that someone else can run this.

2 Likes

Yup, turns out the problem was somewhere else altogether… My had used a Custom Array Type for the lattice whose underlying data I didn’t annotate correctly.