Why is there a performance difference between global and local variables in non-interactive Julia programs?

I’ve found two places in the documentation that say that local is better than than global in terms of performance: Performance Tips says

A global variable might have its value, and therefore its type, change at any point. This makes it difficult for the compiler to optimize code using global variables. Variables should be local, or passed as arguments to functions, whenever possible.

Any code that is performance critical or being benchmarked should be inside a function.

We find that global names are frequently constants, and declaring them as such greatly improves performance.

Similarly, Constants says

The const declaration is allowed on both global and local variables, but is especially useful for globals. It is difficult for the compiler to optimize code involving global variables, since their values (or even their types) might change at almost any time. If a global variable will not change, adding a const declaration solves this performance problem.

Local constants are quite different. The compiler is able to determine automatically when a local variable is constant, so local constant declarations are not necessary for performance purposes.

For interactive sessions in the REPL, I understand how global variables really do need to be kept general, because they could change at any time. But for non-interactive sessions, the distinction between global and local variables seems rather artificial - by placing the entire code inside some function main and then calling main from global scope, all global variables can be converted into formally local ones. This would seem to eliminate the performance problems with global variables and with performance-critical code outside of any functions. Why doesn’t Julia do this automatically?

No the fundamental difference is that eval can observer and mutate global variables but not local variables. Since the sideeffect of eval is undecidable it’s impossible to “convert global variable into local ones”. You can of course speculate and that’s what other JIT does but you can’t do that as a semantically sound transformation.

This sounds promising, but the trivial implementation described here would actually break some codes. One example is with eval. If you do @eval a=2 then because eval is dynamic this takes place in the global (module) scope. So a gets added to the module, not the function. You’d need to global a to get the global a if you also had a local a, so this is different because in the global scope this would have changed the value of a instead of making a new one in a different scope.

However, a fancier version of what you’re describing could work if it works around all of these issues automatically by parsing? I’m not sure if that’s possible, but it would be interesting to look into. I’m sure there’s a counter example where it would have a difference that cannot be determined by parsing, like evaling a mutating function will mutate the global which will then not be in a local of the same name or something like that.

@ChrisRackauckas I see, I was not aware of that subtlety involving eval. Is the documentation’s claim that “local constant declarations are not necessary for performance purposes” unconditionally true, regardless of how complicated the function in which it’s declared is?

No non-speculative version can work without change of semantics. Changing the semantics is almost not an option since it breaks the separation between having maximum flexibility in global scope while having only features that does not break optimizations in local scope (so the features you didn’t use in the local scope will always have zero effect on performance). Implementing a speculative version is possible and is done in basically all other jit’s as I said but it’s of very low priority since we’ll unlikely work on that before we fully optimizes the code that we actually care about (i.e. local scope).

Local constants are not allowed now (deprecated) and had no effect (it was being ignored).
It does not affect the inferrability of the use of the variable since the compiler can see everything about it (no matter how long the function is) and can only affect error finding.

2 Likes