# Compiler optimization for variables and functions

**URL:** <https://discourse.julialang.org/t/compiler-optimization-for-variables-and-functions/14580>\
**Category:** Performance\
**Created:** [September 5, 2018, 12:53pm UTC](https://discourse.julialang.org/t/compiler-optimization-for-variables-and-functions/14580 "2018-09-05T12:53:37Z")\
**Posts on this page:** 17\
**Page:** 1

<div class="post-metadata">

**Author:** ![sairus7](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sairus7/32/10816_2.png) [@sairus7](https://discourse.julialang.org/u/sairus7)\
**Post date:** [September 5, 2018, 12:53pm UTC](https://discourse.julialang.org/t/compiler-optimization-for-variables-and-functions/14580/1 "2018-09-05T12:53:37Z")

</div>

C++ compilers can opt-out some extra variables and unused parts of the code.  
It seems that Julia doesn’t have such optimizations, e.g.:

```
function addone(x::Int)
    local y::Int = 10
    local z::Int = 3
    y = y % x
    x += 1
    x
end

```

Running `@code_native addone(3)` shows that that `rem` function is called, though it has result in local variables.  
Can you explain this?

---

<div class="post-metadata">

**Author:** ![kristoffer.carlsson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kristoffer.carlsson/32/22_2.png) [@kristoffer.carlsson](https://discourse.julialang.org/u/kristoffer.carlsson)\
**Post date:** [September 5, 2018, 1:05pm UTC](https://discourse.julialang.org/t/compiler-optimization-for-variables-and-functions/14580/2 "2018-09-05T13:05:42Z")

</div>

Put things in functions:

```julia
julia> f() = addone(3)
f (generic function with 1 method)

julia> @code_llvm f()

; Function f
; Location: REPL[21]:1
define i64 @julia_f_241591136() {
top:
  ret i64 4
}

```

What you did was equivalent of writing

```julia
code_llvm(addone, Tuple{Int})

```

which doesn’t give the compiler any hint that the about the value of the argument.

---

<div class="post-metadata">

**Author:** ![sairus7](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sairus7/32/10816_2.png) [@sairus7](https://discourse.julialang.org/u/sairus7)\
**Post date:** [September 5, 2018, 1:25pm UTC](https://discourse.julialang.org/t/compiler-optimization-for-variables-and-functions/14580/3 "2018-09-05T13:25:00Z")

</div>

Thanks, but why it’s `Tuple{Int}` ?  
Is it about bad performance with global variables, even when their type is known?  
If I write

```
x = 3 # typeof(x) == Int64
f(x) = addone(x)
@code_llvm f(x)

```

it calls `rem`, but if I wrap it with some `main` function, it becomes constant again:

```
function main()
    x = 3
    addone(x)
end

```

Does this affect `@btime addone($x)` and `@time addone(x)` macro?  
So, should I put any code inside some `main()` or `f()` functions with no input arguments and do NOT benchmark any functions with global input arguments?

---

<div class="post-metadata">

**Author:** ![kristoffer.carlsson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kristoffer.carlsson/32/22_2.png) [@kristoffer.carlsson](https://discourse.julialang.org/u/kristoffer.carlsson)\
**Post date:** [September 5, 2018, 1:36pm UTC](https://discourse.julialang.org/t/compiler-optimization-for-variables-and-functions/14580/4 "2018-09-05T13:36:56Z")

</div>

> [@sairus7](#):
>
> Thanks, but why it’s `Tuple{Int}` ?

The `Tuple{..., ...}` is a list of the argument types to the functions.

> [@sairus7](#):
>
> Is it about bad performance with global variables, even when their type is known?

If it is not `const` the type is not known because you can change it at any point (e.g. by doing `x = 3.0`). If you declare it `const` then it works fine:

```julia
julia> const x = 3
3

julia> f() = addone(x)
f (generic function with 1 method)

julia> @code_llvm f()

; Function f
; Location: REPL[3]:1
define i64 @julia_f_312611515() {
top:
  ret i64 4
}

```

> [@sairus7](#):
>
> So, should I put any code inside some `main()` or `f()` functions with no input arguments and do NOT benchmark any functions with global input arguments?

You should read the [performance tips](https://docs.julialang.org/en/v1/manual/performance-tips/) carefully and use [GitHub - JuliaCI/BenchmarkTools.jl: A benchmarking framework for the Julia language](https://github.com/JuliaCI/BenchmarkTools.jl) to benchmark. But yes, putting things into functions and not using global variables is indeed correct.

---

<div class="post-metadata">

**Author:** ![yuyichao](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yuyichao/32/20_2.png) [@yuyichao](https://discourse.julialang.org/u/yuyichao)\
**Post date:** [September 5, 2018, 1:45pm UTC](https://discourse.julialang.org/t/compiler-optimization-for-variables-and-functions/14580/5 "2018-09-05T13:45:09Z")

</div>

> [@sairus7](#):
>
> Running `@code_native addone(3)` shows that that `rem` function is called, though it has result in local variables.

Which version are you using? We are not very good at recognizing and optimizing out pure functions in general but this case shouldn’t have that problem and on 1.0 no `rem` is called.

What the compiler can’t do though, is to remove the error check. `y % x` throws an error when `x` is `0` and that cannot be optimized out.

> [@kristoffer.carlsson](#):
>
> Put things in functions:

That’s unrelated.

> [@sairus7](#):
>
> Thanks, but why it’s `Tuple{Int}` ?

That’s just how `code_native` the function should be used.

> [@sairus7](#):
>
> Is it about bad performance with global variables, even when their type is known?

No, and none of your example, above or below, uses global variable anyway…

> [@kristoffer.carlsson](#):
>
> You should read the [performance tips](https://docs.julialang.org/en/v1/manual/performance-tips/) carefully and use [https://github.com/JuliaCI/BenchmarkTools.jl](https://github.com/JuliaCI/BenchmarkTools.jl) to benchmark. But yes, putting things into functions and not using global variables is indeed correct.

He didn’t have any of those problems…

---

<div class="post-metadata">

**Author:** ![kristoffer.carlsson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kristoffer.carlsson/32/22_2.png) [@kristoffer.carlsson](https://discourse.julialang.org/u/kristoffer.carlsson)\
**Post date:** [September 5, 2018, 1:47pm UTC](https://discourse.julialang.org/t/compiler-optimization-for-variables-and-functions/14580/6 "2018-09-05T13:47:51Z")

</div>

> [@yuyichao](#):
>
> That’s unrelated.

Yeah, I guess `y` should have been eliminated anyway.

> [@yuyichao](#):
>
> He didn’t have any of those problems…

The answer was about benchmarking in general.

---

<div class="post-metadata">

**Author:** ![sairus7](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sairus7/32/10816_2.png) [@sairus7](https://discourse.julialang.org/u/sairus7)\
**Post date:** [September 5, 2018, 1:49pm UTC](https://discourse.julialang.org/t/compiler-optimization-for-variables-and-functions/14580/7 "2018-09-05T13:49:02Z")

</div>

Ok, but in normal use cases the input value is not known, and even if i write

```
const z = 3
@code_native addone(z)

```

then it calls `rem` on locals.

And even if I put it inside a function and call it with an unknown argument value

```
function main()
    x::Int64 = rand(Int64)
    addone(x)
end

@code_native main()

```

it again calls `rem` on locals…

---

<div class="post-metadata">

**Author:** ![yuyichao](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yuyichao/32/20_2.png) [@yuyichao](https://discourse.julialang.org/u/yuyichao)\
**Post date:** [September 5, 2018, 1:50pm UTC](https://discourse.julialang.org/t/compiler-optimization-for-variables-and-functions/14580/8 "2018-09-05T13:50:14Z")

</div>

> [@kristoffer.carlsson](#):
>
> The answer was about benchmarking in general.

Sure, I know you are talking about benchmark but none of the code above uses global variables… They are the difference between compilers doing constant propagation or not by having the variable as a function input rather than locally known value…

---

<div class="post-metadata">

**Author:** ![yuyichao](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yuyichao/32/20_2.png) [@yuyichao](https://discourse.julialang.org/u/yuyichao)\
**Post date:** [September 5, 2018, 1:58pm UTC](https://discourse.julialang.org/t/compiler-optimization-for-variables-and-functions/14580/9 "2018-09-05T13:58:28Z")

</div>

> [@sairus7](#):
>
> Ok, but in normal use cases the input value is not known, and even if i write

Thare are really multiple problems you are seeing and all what I’m compilaing about @kristoffer.carlsson is that you bring up many issues that unnecessarily complicated the discussion…

For a start, `code_native` (either the function or the macro) does not show you the code that corresponds to the expression you give it, it shows the function you’ll call with it. So having the input to the macro as a constant or not will not change the result.

Secondly, because of this, in no part of any of the code above does global variable play any role. You aren’t seeing any issue caused by type instability so bringing it up is just complicating things… It is a thing to be careful about when doing benchmarks but `@code_native` isn’t a benchmark and you aren’t running the code.

> [@sairus7](#):
>
> And even if I put it inside a function and call it with an unknown argument value

Should go without saying that putting in a function (it already is) does not change the result.

> [@sairus7](#):
>
> then it calls `rem` on locals.

And now the real question. This shouldn’t be the case. Are you just refering to the `rem` in the debug info? or are you actually seeing a `call` to the `rem` function. If it’s the former, it’s just the error issue I said above and if it’s the latter, what’s your version and what’s the code you see?

And finally, something unrelated to the main issue but worth bringing up: none of the `local` and the `::Int` or `::Int64` on the local variables you have are doing anything and you should get rid of them. There are cases where using them is good but in this case it just make the code harder to read (If you are from C and like explicit variable declaration that’s fine but it won’t help the compiler).

---

<div class="post-metadata">

**Author:** ![yuyichao](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yuyichao/32/20_2.png) [@yuyichao](https://discourse.julialang.org/u/yuyichao)\
**Post date:** [September 5, 2018, 2:11pm UTC](https://discourse.julialang.org/t/compiler-optimization-for-variables-and-functions/14580/10 "2018-09-05T14:11:19Z")

</div>

And as for why `code_native` for `addone` and `f() = addone(3)` are different in wrt the `rem` but not in `C`. I believe `%` does not have side effect in C (as in integer divide by 0 is UB in C). However, this is not the case in julia, (integer divide by 0 always throws an error). This means that in C `%` where the result is not used is a no-op and can be removed but in julia that is impossible unless you can proof that there is no error (edit: the error can’t be removed but the actual divide will be). This is why when you check for that explicitly, there will be no code left for the `rem` even if the input is not known.

```julia
julia> function addone(x::Int)
           local y::Int = 10
           local z::Int = 3
           if x != 0
               y = y % x
           end
           x += 1
           x
       end
addone (generic function with 1 method)

julia> @code_llvm addone(3)

; Function addone
; Location: REPL[1]:2
define i64 @julia_addone_36031(i64) {
top:
; Location: REPL[1]:7
; Function +; {
; Location: int.jl:53
  %1 = add i64 %0, 1
;}
; Location: REPL[1]:8
  ret i64 %1
}

```

I also don’t think you are actually seeing a `rem` call in any case since

1. Unless you disabled inlining, `rem` the julia function should be inlined.
2. There isn’t a C function `rem` that you’ll call for this function. On all supported platforms it should be implemented as (inlined) assembly in the llvm backend and you shouldn’t see any call.

This also suggests that since you are probably not super familiar with the assembly code, you should probably use `code_llvm` for this. (This is a case where inference, i.e. `code_warntype`, won’t give you enough info but `code_llvm` is more than enough.)

---

<div class="post-metadata">

**Author:** ![sairus7](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sairus7/32/10816_2.png) [@sairus7](https://discourse.julialang.org/u/sairus7)\
**Post date:** [September 5, 2018, 2:18pm UTC](https://discourse.julialang.org/t/compiler-optimization-for-variables-and-functions/14580/11 "2018-09-05T14:18:30Z")

</div>

Thanks, just as you said, with zero-check it have no `rem`, but without it the output is as follows:

```
; Function addone
; Location: REPL[1]:2
; Function Attrs: uwtable
define i64 @julia_addone_34873(i64) #0 {
top:
; Location: REPL[1]:4
; Function rem; {
; Location: int.jl:233
  %cond = icmp eq i64 %0, 0
  br i1 %cond, label %fail, label %after_srem

fail: ; preds = %top
  call void @jl_throw(%jl_value_t addrspace(12)* addrspacecast (%jl_value_t* inttoptr (i64 142147408 to %jl_value_t*) to %jl_value_t addrspace(12)*))
  unreachable

after_srem: ; preds = %top
;}
; Location: REPL[1]:5
; Function +; {
; Location: int.jl:53
  %1 = add i64 %0, 1
;}
; Location: REPL[1]:6
  ret i64 %1
}

```

Seems like I took the wrong operations to test on local variables…  
By the way, I run Julia 0.7, so it’s not about the version - just the `rem` exception handling.

---

<div class="post-metadata">

**Author:** ![yuyichao](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yuyichao/32/20_2.png) [@yuyichao](https://discourse.julialang.org/u/yuyichao)\
**Post date:** [September 5, 2018, 2:22pm UTC](https://discourse.julialang.org/t/compiler-optimization-for-variables-and-functions/14580/12 "2018-09-05T14:22:23Z")

</div>

And here, there’s no `rem` function call (read, no divide calculated) in your code.  
The `Function rem` here is just the debug info telling you that code is inlined from the `rem` function. So depending on what you mean by `rem` is still there,

1. There’s no `rem` call.
2. There’s no division
3. Part of the `rem` function that **has sideeffect that doesn’t exist in C** is still there.

So the compiler is equally good at removing no-ops but the def of no-op is different and the safer semantics prevented the removal of a branch here (which should be pretty cheap…)

> [@sairus7](#):
>
> Seems like I took the wrong operations to test on local variables…

Yes, pretty much… And you should be able to see the difference by just calling `addone(0)`…

---

<div class="post-metadata">

**Author:** ![sairus7](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sairus7/32/10816_2.png) [@sairus7](https://discourse.julialang.org/u/sairus7)\
**Post date:** [September 5, 2018, 3:00pm UTC](https://discourse.julialang.org/t/compiler-optimization-for-variables-and-functions/14580/13 "2018-09-05T15:00:05Z")

</div>

Ok, so there are some functions, that can’t be dropped-out for optimization.

I tried some number generators such as:  
`y = rand(Int)`  
`y = time_ns()`  
And allocating local arrays:  
`y = zeros(Int, 10, 10)`  
`y = Vector{Int}(undef, 10, 10)`

And none of these have been optimized.  
I wonder if there are some guidance about which variables and functions can be optimized, and which cannot?

---

<div class="post-metadata">

**Author:** ![yuyichao](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yuyichao/32/20_2.png) [@yuyichao](https://discourse.julialang.org/u/yuyichao)\
**Post date:** [September 5, 2018, 3:09pm UTC](https://discourse.julialang.org/t/compiler-optimization-for-variables-and-functions/14580/14 "2018-09-05T15:09:20Z")

</div>

`rand(Int)` cannot be optimized out, it change the state of the global RNG. There isn’t anything defined about this and won’t be. Things that calls external functions will have a hard time to be optimized out

---

<div class="post-metadata">

**Author:** ![sairus7](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sairus7/32/10816_2.png) [@sairus7](https://discourse.julialang.org/u/sairus7)\
**Post date:** [September 5, 2018, 3:27pm UTC](https://discourse.julialang.org/t/compiler-optimization-for-variables-and-functions/14580/15 "2018-09-05T15:27:53Z")

</div>

Also, filling array with some values and overwriting these values with another, have no optimization as well - the native code is slightly longer.

```
function fillone(x::Vector{Int})
    L = length(x)
    v = Int(15)
    for i=1:L
        # x[i] = v # ! no opt-out
        x[i] = 1
    end
    x
end

```

And even with one element:

```
function setone(x::Vector{Int}, i::Int)
    if length(x) < i return x end
    v = Int(15)
    # x[i] = v # ! no opt-out
    x[i] = 1
    x
end

```

I suppose that container types and in particular `setindex!` functions are too complex to optimize.

So, if you use arrays to store some calculation results, these calculations are less optimized.

---

<div class="post-metadata">

**Author:** ![yuyichao](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yuyichao/32/20_2.png) [@yuyichao](https://discourse.julialang.org/u/yuyichao)\
**Post date:** [September 5, 2018, 4:49pm UTC](https://discourse.julialang.org/t/compiler-optimization-for-variables-and-functions/14580/16 "2018-09-05T16:49:34Z")

</div>

> [@sairus7](#):
>
> I suppose that container types and in particular `setindex!` functions are too complex to optimize.

No. And again, the fact that you are not seeing a function call to that means that it’s not that complex.

> [@sairus7](#):
>
> have no optimization as well - the native code is slightly longer.

And no, just don’t do that…

1. As I said, it seems that you don’t actually read assembly all that well, so use `code_llvm` please.
2. It is **never ever** a good idea to just count how many lines there are in, well, basically any code to see what optimization is done. You failed in this once and now again…

You are just seeing the bounds check, which for one reason or another isn’t being optimized out since llvm’s range analysis isn’t super happy about the mix of signed and unsigned comparison…

And again, the “longer” code you see (or at least I see from your code) is just debug info. When you have two `setindex!`, the first one throws the bounds error instead of the second one so you end up with the debug info of two functions instead of one. Again, just stop counting lines and actually read the code. The debug info shows perfectly where you can find the bounds check and the store.

---

<div class="post-metadata">

**Author:** ![sairus7](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sairus7/32/10816_2.png) [@sairus7](https://discourse.julialang.org/u/sairus7)\
**Post date:** [September 5, 2018, 9:56pm UTC](https://discourse.julialang.org/t/compiler-optimization-for-variables-and-functions/14580/17 "2018-09-05T21:56:42Z")

</div>

Ok, thank you for pointing out my mistakes, I will check `code_llvm` then, instead of `code_native`.
