Why do global variables impact performance so badly?

I have two almost identical codes:

#!/usr/bin/env julia

using Random

if(length(ARGS)==1)
    N=parse(Int64,ARGS[1])
else
    N=1000
end

function play()
    r=0
    counter=0.0
    while r<1.0
        r+=rand(Float64)
        counter+=1
    end
    return counter
end

function drive()
    e=0.0
    for i in 1:N
        e+=play()
    end
    println(e/N)
end

@time drive()

with 15.485858 seconds (300.53 M allocations: 5.985 GiB, 0.59% gc time)
and

#!/usr/bin/env julia

using Random

function play()
    r=0.0
    counter=0.0
    while r<1.0
        r+=rand(Float64)
        counter+=1.0
    end
    return counter
end

function drive(N::Int64)
    e=0.0
    for i in 1:N
        e+=play()
    end
    println(e/N)
end

@time drive(length(ARGS)==1 ? parse(Int64,ARGS[1]) : 1000)

with 4.922885 seconds (468.38 k allocations: 21.594 MiB)

Why is the latter a factor three faster – or why does the former use so much memory?

Thanks!

Since N is a non const global it is possible for a value with a different type to get assigned to it. As a result Julia has to generate more generic code that is valid for potentially all types.

3 Likes

Please also read

2 Likes

The answer to this should be that globals are fast as long as they are type annotated. Unfortunately, type annotated globals aren’t supported yet. There’s no great reason why not other than no one has implemented it yet.

1 Like

What do you mean by type-annotated globals?

You can define const globals, the type (and the value) of a const global is assumed to be unchanging. You only refer to a global after you define it. Consequently, const globals are de facto type annotated.

Do you mean: “we could have non-const fast globals in the future, their only restriction is that they cannot change type (but they could change value)”? We kinda already have that, you just need to make a global const N = Ref(parse(Int64,ARGS[1])) (the value is accessed by N[] and can be changed by N[] = new_value). Its type and value are constant (but its value is, in fact, a memory address), and the value pointed by the memory address can be changed safely, basically giving you a non-const type-annotated global.

what I mean is that i::int=0 doesn’t work in the global scope. If it did, you could have fast non-const globals since the compiler would know their type.

Ok. As I have pointed out above, the reason probably nobody implemented them yet, is just there already is a good workaround.

Indeed placing const N = length(ARGS)==1 ? parse(Int64,ARGS[1]) : 1000 fixes the performance! Thank you!

1 Like

Global variables are a bad practice and should be avoided in any programming language.
For the rare occasions they are really needed the Ref syntax is available in Julia.
Even if there would be ways to generally speed up global variables in Julia, this would take up valuable time from the core devs and encourage bad programming styles.

2 Likes

Ironically, I would consider the Ref workaround shown above also bad programming style (more so as it is caused by non-const untyped globals being so slow).

And the only time use of global variables is really a bad practice is if you write code using globals where the code might get reused as a module, in which case the globals can cause trouble. For standalone scripts whose code will never get reused it’s no issue. The OP’s example of initializing a global variable N in a standalone script is valid and it’s a shame that this have to be avoided due to performance consequences.

2 Likes

I am all for people adopting good programming practices and avoiding bad programming practices. I just hope they understand why they are considered either, instead taking advice that is never absolute as an absolute rule.

1 Like

I guess absolute statements elicit absolute responses :wink: