Slow down from parametric type

I have a World-like object, that is passed without modification to most of the functions in my codebase. It has four fields, which I’ll call air, fire, water, earth. Each of these is heavily parametric (typeof(world.air) takes a whole paragraph). If I define

struct World{A, F, W}
   air::A
   fire::F
   water::W
   earth
end

my benchmark suite (on the second run to take out the compilation time) takes 133 seconds. But if I make World parametric in earth (World{A, F, W, E}), it takes 250 seconds. Profiling this code effectively is a challenge (it’s more-or-less an interpreter for a DSL). What are the likely causes for such a slowdown? There’s only one World type, whether it’s parametric in earth or not, so there shouldn’t be more methods defined.

Recompiling too much?

I’m running the same code twice for each version, and reporting a slowdown on the second run, so there shouldn’t be any compilation involed, right?

Yeah, unless there’s something dynamic going on.

If you can run the code, why can’t you profile it?

I can, and do profile it, but it’s painful (because it’s essentially an interpreter), so I was hoping for a clue to guide my search.

Just a guess: it’s possible that after your change, your code is no longer correctly inferring the concrete type of some World instance. One way that could happen is if somewhere you’re storing a Vector{World{A, F, W}} not a Vector{World{A, F, W, E}}.

@code_warntype should show you if that’s the case in a given function.

2 Likes

Narrowed it down. It’s probably a compiler heuristic working against my use case:

https://github.com/JuliaLang/julia/issues/21634