How to let the compiler know that a global variable is type-stable for duration of function

I’m trying to run a simulation on a process where a process stream can contain various chemical components. For example, ethanol (C2H5OH) and water (H2). There’s a lot of places in the simulation where it makes sense to have things sitting in vectors that can be indexed by these components. For example

stream.massFlows[ indexer[:C2H5OH] ]
stream.densities[ indexer[:C2H5OH] ]

As we progress in modelling, these components can change, but they change very rarely. As I plan to have this running on a server, it would be nice if I could change these components without having to completely reload Julia. So the indexer becomes a global “nearly constant” variable (and is definitely constant for the duration of any function execution that uses it). This is pretty performance-sensitive as these references will happen inside an optimizer.

I tried using a NamedTuple as a constant, but if I change the indices, the actual type changes which is not allowed (I’d have to reload Julia). Ideally, all a change in the indexer would do is compile new methods for functions that use the indexer. However, I don’t want to be explicitly passing the indexer as an argument all the time.

So among the options I know of, I have:

(1) Global variable INDEXER that consists of named tuples (name to index) and use it in a function. I’ve heard this is inefficient, because the optimizer has trouble with it. Is it because the function that uses a global variable recompiles a new method if the type changes? If that’s the case, that’s what I want. INDEXER will maintain the same type during the execution, but it may change between executions.

(2) Global variable INDEXER passed as an optional named argument
myfunc(...; indexer=INEXER)
Does a change in INDEXER type cause multiple dispatch to create a new method like I want? Is this the only way to get this behavior?

(3) Global constant dictionary INDEXER that consists of symbols and Integer indices. I heard this could be pretty inefficient if you’re just looking at a small set of variables (which I am).

(4) Global constant list COMPONENTS that merely contains list symbols and at the beginning of every function I write " indexer = NamedTuple{COMPONENTS}(1:length(COMPONENTS)) "

I’m thinking that Option (2) is probably the most performant if it works. Are there any other (potentially better) options I might consider? Is Option (2) the “Official” Julia way of handling a global variable that doesn’t change type (or values for that matter) within a function execution?

I usually make use of https://github.com/jonniedie/ComponentArrays.jl for this purpose. I’m not entirely sure how it works with your dynamically changing indices, but it might be worth checking out.

I would use something like this, but it has to sit inside another object with concrete types. Life is simpler in this case if you simply place arrays in these fields and abstract-out the named indexing only to the places where you actually need it. Most of the time, I can get by with plain arrays, but I’ll definitely need named indexing if I’m dealing with chemical reactions and I’m trying to work through some separation process too to see if I can get by without.

Pass it to a function which then gets a concrete type (effectively a function barrier). Sketch:

GLOBAL = 1

function do_stuff(x)
    ...
end

do_stuff(GLOBAL)
2 Likes

Another trick is to use const Ref(value)

3 Likes

It could be a good practice to mark every global binding constant, where mutable globals can be achieved with what @baggepinnen showed. Function and struct definitions are themselves constant.

On the other hand, a non-constant global variable may be particularly rare in practice. It may be hard to see a program that requires a global binding which needs to change later.

Basically, Option (2) is a fancy version of the “function barrier” solution you mentioned. I think it’s the option I’m going to go with.