# Performance of global variables as default argument values

**URL:** <https://discourse.julialang.org/t/performance-of-global-variables-as-default-argument-values/80399>\
**Category:** General Usage\
**Tags:** performance\
**Created:** [May 3, 2022, 4:21am UTC](https://discourse.julialang.org/t/performance-of-global-variables-as-default-argument-values/80399 "2022-05-03T04:21:15Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![jbu](https://avatars.discourse-cdn.com/v4/letter/j/a8b319/32.png) [@jbu](https://discourse.julialang.org/u/jbu)\
**Post date:** [May 3, 2022, 4:21am UTC](https://discourse.julialang.org/t/performance-of-global-variables-as-default-argument-values/80399/1 "2022-05-03T04:21:16Z")

</div>

Is there a performance penalty for using non-const global variables as default argument values in functions? For example, consider the following snippet:

```julia
globalval = 10

function addxy(x, y=globalval)
    return x + y
end

println(addxy(10))

```

Should `globalval` be declared `const` (as per the [performance tips](https://docs.julialang.org/en/v1/manual/performance-tips/#Avoid-global-variables)) for the compiler to best optimize code? Or are type annotations needed?

I find this case interesting because `globalval` is technically passed as an argument to the function, but the function behavior and return type could change depending on the type of `globalval`. I couldn’t tell from [the documentation](https://docs.julialang.org/en/v1/manual/methods/#Note-on-Optional-and-keyword-Arguments) whether this required mitigation to the same level as if `globalval` was captured by `addxy`.

(Edit: I am by no means claiming this is a good coding pattern. It would still be good to understand what’s happening though.)

---

<div class="post-metadata">

**Author:** ![ffevotte](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ffevotte/32/6587_2.png) [@ffevotte](https://discourse.julialang.org/u/ffevotte)\
**Post date:** [May 3, 2022, 6:25am UTC](https://discourse.julialang.org/t/performance-of-global-variables-as-default-argument-values/80399/2 "2022-05-03T06:25:26Z")

</div>

> [@jbu](#):
>
> Is there a performance penalty for using non-const global variables as default argument values in functions?

> [@jbu](#):
>
> I find this case interesting because `globalval` is technically passed as an argument to the function, but the function behavior and return type could change depending on the type of `globalval` .

Yes, I think there will be a performance penalty, for exactly the reason you mention: having the default argument value be a global variable makes the 1-argument method of your function type-unstable:

```julia
julia> globalval = 10;

julia> addxy(x, y=globalval) = x+y;

julia> methods(addxy)
# 2 methods for generic function "addxy":
[1] addxy(x) in Main at REPL[3]:1 # this method is type-unstable (see below)
[2] addxy(x, y) in Main at REPL[3]:1 # this one is not

julia> @code_warntype addxy(10)
MethodInstance for addxy(::Int64)
  from addxy(x) in Main at REPL[3]:1
Arguments
  #self#::Core.Const(addxy)
  x::Int64
Body::Any
1 ─ %1 = (#self#)(x, Main.globalval)::Any
└── return %1

```

  

> [@jbu](#):
>
> Should `globalval` be declared `const` (as per the [performance tips](https://docs.julialang.org/en/v1/manual/performance-tips/#Avoid-global-variables)) for the compiler to best optimize code?

Yes, that would be a way to avoid the type instability:

```julia
julia> const globalval2 = 11;

julia> addxy2(x, y=globalval2) = x+y;

julia> @code_warntype addxy2(10)
MethodInstance for addxy2(::Int64)
  from addxy2(x) in Main at REPL[7]:1
Arguments
  #self#::Core.Const(addxy2)
  x::Int64
Body::Int64
1 ─ %1 = (#self#)(x, Main.globalval2)::Int64
└── return %1

```

  

If the default value can actually change (in type and/or value), another possibility could be to use a small helper function to define the default value:

```julia
julia> defaultval() = 12;

julia> addxy3(x, y=defaultval()) = x+y;

julia> @code_warntype addxy3(10)
MethodInstance for addxy3(::Int64)
  from addxy3(x) in Main at REPL[10]:1
Arguments
  #self#::Core.Const(addxy3)
  x::Int64
Body::Int64
1 ─ %1 = Main.defaultval()::Core.Const(12)
│ %2 = (#self#)(x, %1)::Int64
└── return %2

```

---

<div class="post-metadata">

**Author:** ![greg\_plowman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/greg_plowman/32/8100_2.png) [@greg\_plowman](https://discourse.julialang.org/u/greg_plowman)\
**Post date:** [May 4, 2022, 11:20pm UTC](https://discourse.julialang.org/t/performance-of-global-variables-as-default-argument-values/80399/3 "2022-05-04T23:20:25Z")

</div>

You could also restrict the type in the function argument:

```julia
addxy4(x, y::Int=globalval) = x + y

```

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [May 5, 2022, 12:13am UTC](https://discourse.julialang.org/t/performance-of-global-variables-as-default-argument-values/80399/4 "2022-05-05T00:13:45Z")

</div>

> [@greg\_plowman](#):
>
> You could also restrict the type in the function argument:

This is equivalent to

```julia
addxy4(x, y::Int) = x + y
addxy4(x) = addxy4(x, globalval)

```

which will still have the overhead of a dynamic lookup of the type of `globalval` — it has to decide at _runtime_ whether to call `addxy4(x, y::Int)` or throw a method error.

So it’s still better to make `globalval` a `const` (or a typed global in Julia 1.8).

---

<div class="post-metadata">

**Author:** ![greg\_plowman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/greg_plowman/32/8100_2.png) [@greg\_plowman](https://discourse.julialang.org/u/greg_plowman)\
**Post date:** [May 5, 2022, 12:34am UTC](https://discourse.julialang.org/t/performance-of-global-variables-as-default-argument-values/80399/5 "2022-05-05T00:34:51Z")

</div>

Yes, thanks for pointing this out.

---

<div class="post-metadata">

**Author:** ![jbu](https://avatars.discourse-cdn.com/v4/letter/j/a8b319/32.png) [@jbu](https://discourse.julialang.org/u/jbu)\
**Post date:** [May 5, 2022, 2:38am UTC](https://discourse.julialang.org/t/performance-of-global-variables-as-default-argument-values/80399/6 "2022-05-05T02:38:20Z")

</div>

Thanks for this information!

> [@ffevotte](#):
>
> Yes, I think there will be a performance penalty, for exactly the reason you mention: having the default argument value be a global variable makes the 1-argument method of your function type-unstable:

Just to clarify, is the main issue here type instability? Or is it the dynamic lookup of the type of `globalval` as @stevengj mentioned?

Ignoring `globalval`, the function `addxy(x,y)` seems to be type stable–the output type only depends on the types of `x` and `y`, and is independent of the _values_ of `x` and `y`. The main issue then seems to be looking up the type of `globalval` at runtime.

---

<div class="post-metadata">

**Author:** ![ffevotte](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ffevotte/32/6587_2.png) [@ffevotte](https://discourse.julialang.org/u/ffevotte)\
**Post date:** [May 5, 2022, 6:48am UTC](https://discourse.julialang.org/t/performance-of-global-variables-as-default-argument-values/80399/7 "2022-05-05T06:48:38Z")

</div>

> [@jbu](#):
>
> Just to clarify, is the main issue here type instability? Or is it the dynamic lookup of the type of `globalval` as @stevengj mentioned?

I think we’re actually speaking of the same thing

  

> [@jbu](#):
>
> Ignoring `globalval` , the function `addxy(x,y)` seems to be type stable–the output type only depends on the types of `x` and `y` , and is independent of the _values_ of `x` and `y` . The main issue then seems to be looking up the type of `globalval` at runtime.

Let me try and be clearer this time: you’re right that there is no type-stability issue with the 2-argument `addxy(x,y)` method. However, when you’re defining a 2-argument method with a default value for the second argument, Julia automatically defines for you a 1-argument method that handles cases where the second argument is not provided. That is why we see two methods here:

```julia
julia> addxy(x, y=globalval) = x + y
addxy (generic function with 2 methods)

julia> methods(addxy)
# 2 methods for generic function "addxy":
[1] addxy(x) in Main at REPL[1]:1
[2] addxy(x, y) in Main at REPL[1]:1

```

The 1-argument method (listed first above) has been added for you automatically, with a definition similar to:

```julia
addxy(x) = addxy(x, globalval)

```

This method is not type-stable, in the sense that its return type will not depend only on the type of `x` (its only argument), but also on the result of a dynamic lookup of the type of `globalval`. Hence the performance penalty _when (and only when) you’re calling your function with one argument, relying on the (global) default value for the second argument_.

Does that make more sense?

---

<div class="post-metadata">

**Author:** ![jbu](https://avatars.discourse-cdn.com/v4/letter/j/a8b319/32.png) [@jbu](https://discourse.julialang.org/u/jbu)\
**Post date:** [May 6, 2022, 2:51am UTC](https://discourse.julialang.org/t/performance-of-global-variables-as-default-argument-values/80399/8 "2022-05-06T02:51:51Z")

</div>

Thanks for this explanation! 😀 This is helping to clarify things.

Still trying to make sure I have the correct mental model. Totally makes sense that `addxy(x)` is turned into a call to `addxy(x,globalval)`, and the output type will depend on the type of `globalval` in addition to `x`.

Looking at the docs here for [function calls](https://docs.julialang.org/en/v1/devdocs/functions/#Function-calls), if I call `addxy(x,y)` with two arguments, the tuple `Tuple{typeof(addxy), typeof(x), typeof(y)}` is formed. So there’s a lookup of three types: `typeof(addxy)`, `typeof(x)` and `typeof(y)`. The correct method is then chosen based on these three types.

If I call `addxy(x)` with only one argument, this function is turned into the call `addxy(x,globalval)`. This also will involve a lookup of three types: `typeof(addxy)`, `typeof(x)`, and `typeof(globalval)`. The correct method will then be chosen based on these three types.

The main difference I see between the two calls `addxy(x,y)` and `addxy(x)` then is `typeof(y)` versus `typeof(globalval)`. Is this the primary source of the performance penalty when `globalval` is non-const?

---

<div class="post-metadata">

**Author:** ![ffevotte](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ffevotte/32/6587_2.png) [@ffevotte](https://discourse.julialang.org/u/ffevotte)\
**Post date:** [May 6, 2022, 8:54am UTC](https://discourse.julialang.org/t/performance-of-global-variables-as-default-argument-values/80399/9 "2022-05-06T08:54:11Z")

</div>

> [@jbu](#):
>
> The main difference I see between the two calls `addxy(x,y)` and `addxy(x)` then is `typeof(y)` versus `typeof(globalval)` . Is this the primary source of the performance penalty when `globalval` is non-const?

AFIU, yes. And I’d say that the implications of having to dynamically dispatch based on `typeof(globalval)` are twofold:

- it involves a lookup at runtime, which costs something in itself, and
- it prevents the compiler from knowing at compile-time what will be the type of the return value, which in turn prevents further optimizations down the road (in the context of the caller)

```julia
globalval = 10

function addxy(x, y=globalval)
    return x + y
end

function foo(x)
   z = addxy(10) # (1) which specialization of addxy(x,y) to choose based on `typeof(globalval)`?
   return z + 1 # (2) which method of + to choose based on `typeof(z)`?
end

```
